summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src')
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/framework/fixture.ts17
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/framework/test_config.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/file_loader.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/logging/log_message.ts51
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/logging/logger.ts5
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/logging/result.ts22
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/logging/test_case_recorder.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/query/compare.ts5
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/query/parseQuery.ts43
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/query/query.ts6
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/test_group.ts10
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/test_suite_listing.ts2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/tree.ts7
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/internal/websocket_logger.ts13
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/cmdline.ts52
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/helper/options.ts61
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker-worker.ts49
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker.ts202
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/helper/utils_worker.ts35
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/helper/wrap_for_worker.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/server.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/standalone.ts53
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/wpt.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/tools/crawl.ts56
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/tools/dev_server.ts13
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/tools/gen_cache.ts278
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/tools/gen_listings.ts11
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/tools/gen_listings_and_webworkers.ts89
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/tools/gen_wpt_cts_html.ts135
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/tools/merge_listing_times.ts12
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/tools/validate.ts29
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/util/crc32.ts57
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/util/parse_imports.ts36
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/util/util.ts5
-rw-r--r--dom/webgpu/tests/cts/checkout/src/external/README.md2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/LICENSE.txt2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.d.ts27
-rw-r--r--dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.js181
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/README.md100
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/hashes.json111
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/abs.binbin0 -> 17976 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/acos.binbin0 -> 26440 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/acosh.binbin0 -> 30248 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/asin.binbin0 -> 26440 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/asinh.binbin0 -> 11664 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atan.binbin0 -> 20128 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atan2.binbin0 -> 51608 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atanh.binbin0 -> 21104 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_addition.binbin0 -> 237960 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_division.binbin0 -> 77496 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_logical.binbin0 -> 12456 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_addition.binbin0 -> 175672 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_matrix_multiplication.binbin0 -> 82608 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_scalar_multiplication.binbin0 -> 291528 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_subtraction.binbin0 -> 181112 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_vector_multiplication.binbin0 -> 152528 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_multiplication.binbin0 -> 207864 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_remainder.binbin0 -> 77496 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_subtraction.binbin0 -> 237960 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/ai_arithmetic.binbin0 -> 831992 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_addition.binbin0 -> 169680 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_division.binbin0 -> 120856 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_logical.binbin0 -> 15912 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_addition.binbin0 -> 557464 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_matrix_multiplication.binbin0 -> 1481368 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_scalar_multiplication.binbin0 -> 906440 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_subtraction.binbin0 -> 534232 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_vector_multiplication.binbin0 -> 601680 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_multiplication.binbin0 -> 171072 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_remainder.binbin0 -> 118944 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_subtraction.binbin0 -> 169680 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_addition.binbin0 -> 279984 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_division.binbin0 -> 174960 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_logical.binbin0 -> 18984 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_addition.binbin0 -> 991896 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_matrix_multiplication.binbin0 -> 2510560 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_scalar_multiplication.binbin0 -> 1534768 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_subtraction.binbin0 -> 995984 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_vector_multiplication.binbin0 -> 950336 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_multiplication.binbin0 -> 257976 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_remainder.binbin0 -> 171384 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_subtraction.binbin0 -> 279968 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/i32_arithmetic.binbin0 -> 727864 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/i32_comparison.binbin0 -> 3872 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/u32_arithmetic.binbin0 -> 225816 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/u32_comparison.binbin0 -> 2192 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/bitcast.binbin0 -> 2240896 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/ceil.binbin0 -> 16016 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/clamp.binbin0 -> 1048136 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cos.binbin0 -> 20176 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cosh.binbin0 -> 14904 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cross.binbin0 -> 829096 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/degrees.binbin0 -> 18048 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/derivatives.binbin0 -> 11264 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/determinant.binbin0 -> 9944 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/distance.binbin0 -> 1731496 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/dot.binbin0 -> 469272 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/exp.binbin0 -> 34592 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/exp2.binbin0 -> 34592 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/faceForward.binbin0 -> 4214688 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/floor.binbin0 -> 16016 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/fma.binbin0 -> 886720 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/fract.binbin0 -> 16408 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/frexp.binbin0 -> 47072 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/inverseSqrt.binbin0 -> 78424 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/ldexp.binbin0 -> 33096 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/length.binbin0 -> 35696 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/log.binbin0 -> 148848 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/log2.binbin0 -> 148848 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/max.binbin0 -> 37944 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/min.binbin0 -> 37944 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/mix.binbin0 -> 5817432 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/modf.binbin0 -> 82448 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/normalize.binbin0 -> 47608 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/pack2x16float.binbin0 -> 1791400 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/pow.binbin0 -> 1308224 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/quantizeToF16.binbin0 -> 8904 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/radians.binbin0 -> 10992 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/reflect.binbin0 -> 277720 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/refract.binbin0 -> 2828888 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/round.binbin0 -> 14816 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/saturate.binbin0 -> 17096 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sign.binbin0 -> 21224 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sin.binbin0 -> 20176 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sinh.binbin0 -> 14904 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/smoothstep.binbin0 -> 629408 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sqrt.binbin0 -> 10024 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/step.binbin0 -> 33160 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/tan.binbin0 -> 21776 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/tanh.binbin0 -> 11448 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/transpose.binbin0 -> 129192 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/trunc.binbin0 -> 14696 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/af_arithmetic.binbin0 -> 30376 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/af_assignment.binbin0 -> 10296 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/ai_arithmetic.binbin0 -> 3256 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/ai_assignment.binbin0 -> 6936 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/bool_conversion.binbin0 -> 6480 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f16_arithmetic.binbin0 -> 15208 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f16_conversion.binbin0 -> 97912 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f32_arithmetic.binbin0 -> 19544 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f32_conversion.binbin0 -> 105744 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/i32_arithmetic.binbin0 -> 1648 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/i32_conversion.binbin0 -> 11856 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/u32_conversion.binbin0 -> 10680 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16float.binbin0 -> 4832 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16snorm.binbin0 -> 4968 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16unorm.binbin0 -> 4968 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack4x8snorm.binbin0 -> 7416 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack4x8unorm.binbin0 -> 7416 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-hflip.mp4bin0 -> 3174 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-180.mp4bin16261 -> 3113 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-270.mp4bin16261 -> 3211 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-90.mp4bin16261 -> 3204 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-vflip.mp4bin0 -> 3174 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601.mp4bin16261 -> 3174 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-theora-bt601.ogvbin44488 -> 0 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp8-bt601.webmbin17910 -> 2421 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-hflip.mp4bin0 -> 2077 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-180.mp4bin0 -> 2079 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-270.mp4bin0 -> 2016 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-90.mp4bin0 -> 2079 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-vflip.mp4bin0 -> 2077 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601.mp4bin0 -> 2077 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601.webmbin13116 -> 1847 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt709.webmbin12584 -> 1789 bytes
-rw-r--r--dom/webgpu/tests/cts/checkout/src/unittests/conversion.spec.ts14
-rw-r--r--dom/webgpu/tests/cts/checkout/src/unittests/crc32.spec.ts28
-rw-r--r--dom/webgpu/tests/cts/checkout/src/unittests/floating_point.spec.ts2285
-rw-r--r--dom/webgpu/tests/cts/checkout/src/unittests/maths.spec.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/unittests/parse_imports.spec.ts79
-rw-r--r--dom/webgpu/tests/cts/checkout/src/unittests/serialization.spec.ts69
-rw-r--r--dom/webgpu/tests/cts/checkout/src/unittests/texture_ok.spec.ts31
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/adapter/requestDevice.spec.ts59
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts113
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/image_copy.spec.ts94
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/queries/occlusionQuery.spec.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/memory_sync/texture/readonly_depth_stencil.spec.ts329
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/reflection.spec.ts293
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts24
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/3d_texture_slices.spec.ts363
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/color_target_state.spec.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth.spec.ts9
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_bias.spec.ts12
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_clip_clamp.spec.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_copy.ts3
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_ds_test.ts3
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_sampling.ts38
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/texture_zero_init_test.ts548
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/texture_zero.spec.ts554
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/shader_module/compilation_info.spec.ts39
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/storage_texture/read_only.spec.ts626
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/storage_texture/read_write.spec.ts385
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/format_reinterpretation.spec.ts10
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/write.spec.ts347
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/buffer/mapping.spec.ts39
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/features/query_types.spec.ts56
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/limit_utils.ts119
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxBindGroups.spec.ts156
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxBindGroupsPlusVertexBuffers.spec.ts301
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupStorageSize.spec.ts9
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxInterStageShaderComponents.spec.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSampledTexturesPerShaderStage.spec.ts50
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSamplersPerShaderStage.spec.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageBuffersPerShaderStage.spec.ts85
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageTexturesPerShaderStage.spec.ts46
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxUniformBuffersPerShaderStage.spec.ts46
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxVertexBuffers.spec.ts68
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/compute_pipeline.spec.ts50
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroup.spec.ts49
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroupLayout.spec.ts47
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createTexture.spec.ts43
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createView.spec.ts11
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts15
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/createRenderBundleEncoder.spec.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/encoder_open_state.spec.ts14
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts343
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/queries/general.spec.ts42
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/render_bundle.spec.ts14
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/error_scope.spec.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/gpu_external_texture_expiration.spec.ts35
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/image_copy.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/texture_related.spec.ts2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/layout_shader_compat.spec.ts287
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/query_set.spec.ts53
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/texture.spec.ts9
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/attachment_compatibility.spec.ts7
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts190
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/common.ts11
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/depth_stencil_state.spec.ts12
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts77
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/inter_stage.spec.ts72
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/misc.spec.ts34
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/resource_compatibility.spec.ts95
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_pass_encoder.spec.ts269
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_common.spec.ts101
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.ts307
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/shader_module/entry_point.spec.ts226
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/state/device_lost/destroy.spec.ts12
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/texture/bgra8unorm_storage.spec.ts31
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/utils.ts275
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/validation_test.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/capability_info.ts125
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/createBindGroup.spec.ts178
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/createBindGroupLayout.spec.ts34
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/encoding/cmds/copyTextureToBuffer.spec.ts9
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/encoding/cmds/copyTextureToTexture.spec.ts94
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/render_pipeline/depth_stencil_state.spec.ts53
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/render_pipeline/shader_module.spec.ts207
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/texture/createTexture.spec.ts131
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/constants.ts5
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/format_info.ts1147
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/gpu_test.ts232
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/idl/constructable.spec.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/listing_meta.json724
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/multisample_info.ts75
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/print_environment.spec.ts70
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/array/index.spec.ts354
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/matrix/index.spec.ts200
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/structure/index.spec.ts508
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/vector/components.spec.ts118
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/vector/index.spec.ts87
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_addition.cache.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts85
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_comparison.cache.ts90
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_comparison.spec.ts141
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_division.cache.ts57
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_division.spec.ts85
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_addition.cache.ts26
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_addition.spec.ts34
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_matrix_multiplication.cache.ts29
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_matrix_multiplication.spec.ts45
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_scalar_multiplication.cache.ts49
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_scalar_multiplication.spec.ts69
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.cache.ts26
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.spec.ts34
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_vector_multiplication.cache.ts51
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_vector_multiplication.spec.ts69
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_multiplication.cache.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts85
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_remainder.cache.ts57
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_remainder.spec.ts83
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_subtraction.cache.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts85
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_arithmetic.cache.ts145
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_arithmetic.spec.ts303
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_comparison.spec.ts124
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/binary.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise.spec.ts505
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise_shift.spec.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bool_logical.spec.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_addition.cache.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_addition.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_comparison.cache.ts144
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_comparison.spec.ts159
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_division.cache.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_division.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_addition.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_addition.spec.ts34
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.cache.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.spec.ts36
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.cache.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.spec.ts59
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.spec.ts34
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.cache.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.spec.ts59
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_multiplication.cache.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_multiplication.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_remainder.cache.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_remainder.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_subtraction.cache.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_subtraction.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_addition.cache.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_comparison.cache.ts144
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_comparison.spec.ts159
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_division.cache.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_division.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_addition.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_addition.spec.ts34
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.cache.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.spec.ts36
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.cache.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.spec.ts59
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.spec.ts34
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.cache.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.spec.ts59
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_multiplication.cache.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_remainder.cache.ts64
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_subtraction.cache.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_arithmetic.cache.ts306
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_arithmetic.spec.ts392
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_comparison.cache.ts21
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_comparison.spec.ts36
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_arithmetic.cache.ts293
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_arithmetic.spec.ts379
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_comparison.cache.ts21
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_comparison.spec.ts36
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.cache.ts26
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts64
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.cache.ts24
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts57
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.cache.ts24
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts56
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.cache.ts24
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts57
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.cache.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts41
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.cache.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts52
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.cache.ts35
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts56
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.cache.ts32
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts63
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts7
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.cache.ts837
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts1142
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.cache.ts26
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts79
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.cache.ts131
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts140
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts57
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts47
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts6
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts6
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts6
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.cache.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts79
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.cache.ts24
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts52
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.cache.ts14
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.ts215
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.cache.ts99
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts112
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.cache.ts49
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts208
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.cache.ts118
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts238
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4I8Packed.spec.ts74
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4U8Packed.spec.ts59
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts16
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts16
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts16
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts16
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts16
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts16
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.cache.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts69
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.cache.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts69
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.cache.ts125
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts198
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts6
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts6
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.cache.ts26
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts61
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.cache.ts26
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts70
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.cache.ts50
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts81
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.cache.ts103
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts304
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.cache.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.cache.ts61
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts94
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.cache.ts42
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts146
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.cache.ts30
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.cache.ts30
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.cache.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts106
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.cache.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts106
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.cache.ts56
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts210
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.cache.ts75
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts192
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.cache.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts88
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.cache.ts55
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts68
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts15
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts15
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8.spec.ts69
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8Clamp.spec.ts73
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8.spec.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8Clamp.spec.ts57
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.cache.ts24
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts67
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.cache.ts41
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts46
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.cache.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.cache.ts26
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts151
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.cache.ts116
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts191
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts6
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.cache.ts24
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts55
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.cache.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts56
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts129
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.cache.ts31
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts66
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts57
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts47
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.cache.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts71
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts47
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.cache.ts41
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts66
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.cache.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts57
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.cache.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts41
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.ts160
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimensions.spec.ts518
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts99
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts22
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts23
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/texture_utils.ts809
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.cache.ts27
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts85
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.cache.ts17
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts39
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.cache.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.cache.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.cache.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.cache.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.cache.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xI8.spec.ts56
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xU8.spec.ts48
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/workgroupUniformLoad.spec.ts182
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/user/ptr_params.spec.ts849
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/case.ts440
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/case_cache.ts39
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/constructor/non_zero.spec.ts797
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/constructor/zero_value.spec.ts162
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/expectation.ts38
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/expression.ts800
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/interval_filter.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/precedence.spec.ts113
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/address_of_and_indirection.spec.ts171
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.cache.ts13
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts30
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.cache.ts51
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts66
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.cache.ts11
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.spec.ts30
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.cache.ts21
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.spec.ts65
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_complement.spec.ts32
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.cache.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.spec.ts87
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_logical.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.cache.ts13
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.cache.ts135
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.spec.ts226
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.cache.ts13
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.spec.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.cache.ts79
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.spec.ts113
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.cache.ts11
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.spec.ts15
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_complement.spec.ts17
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.cache.ts116
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.spec.ts158
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_complement.spec.ts17
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.cache.ts107
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.spec.ts144
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/unary.ts8
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/call.spec.ts113
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/for.spec.ts50
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/loop.spec.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/switch.spec.ts33
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/while.spec.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_layout.spec.ts1059
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/barrier.spec.ts179
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/memory_model_setup.ts341
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/texture_intra_invocation_coherence.spec.ts333
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access.spec.ts30
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access_vertex.spec.ts1
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts175
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts1410
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/user_io.spec.ts213
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/workgroup_size.spec.ts150
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/stage.spec.ts133
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/statement/compound.spec.ts137
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/statement/discard.spec.ts645
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/zero_init.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/types.ts206
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/const_assert/const_assert.spec.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/compound_statement.spec.ts98
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/const.spec.ts158
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/context_dependent_resolution.spec.ts338
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/let.spec.ts180
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/override.spec.ts178
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/var.spec.ts529
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/access/vector.spec.ts3
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/add_sub_mul.spec.ts320
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/and_or_xor.spec.ts182
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/bitwise_shift.spec.ts182
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/comparison.spec.ts186
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/div_rem.spec.ts279
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/abs.spec.ts114
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acos.spec.ts162
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acosh.spec.ts158
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/all.spec.ts191
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/any.spec.ts191
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/arrayLength.spec.ts109
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asin.spec.ts161
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asinh.spec.ts153
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan.spec.ts146
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan2.spec.ts293
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atanh.spec.ts169
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atomics.spec.ts235
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/barriers.spec.ts109
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/ceil.spec.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/clamp.spec.ts14
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/const_override_validation.ts292
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cos.spec.ts59
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cosh.spec.ts66
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countLeadingZeros.spec.ts198
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countOneBits.spec.ts198
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countTrailingZeros.spec.ts198
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cross.spec.ts122
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/degrees.spec.ts56
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/derivatives.spec.ts129
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/determinant.spec.ts95
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/distance.spec.ts149
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/dot4I8Packed.spec.ts66
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/dot4U8Packed.spec.ts66
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp.spec.ts122
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp2.spec.ts122
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/extractBits.spec.ts218
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/faceForward.spec.ts152
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/firstLeadingBit.spec.ts198
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/firstTrailingBit.spec.ts198
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/floor.spec.ts108
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/fract.spec.ts94
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/frexp.spec.ts94
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/insertBits.spec.ts241
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/inverseSqrt.spec.ts76
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/length.spec.ts58
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log.spec.ts50
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log2.spec.ts50
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/max.spec.ts91
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/min.spec.ts91
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/modf.spec.ts17
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/normalize.spec.ts146
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack2x16snorm.spec.ts58
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack2x16unorm.spec.ts58
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4x8snorm.spec.ts58
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4x8unorm.spec.ts58
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xI8.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xI8Clamp.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xU8.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xU8Clamp.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/quantizeToF16.spec.ts113
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/radians.spec.ts50
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/reflect.spec.ts131
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/reverseBits.spec.ts198
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/round.spec.ts31
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/saturate.spec.ts17
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/select.spec.ts250
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/shader_stage_utils.ts64
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sign.spec.ts63
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sin.spec.ts60
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sinh.spec.ts66
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/smoothstep.spec.ts241
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sqrt.spec.ts66
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/step.spec.ts108
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/tan.spec.ts76
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/tanh.spec.ts98
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureGather.spec.ts335
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureGatherCompare.spec.ts264
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureLoad.spec.ts370
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSample.spec.ts267
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleBaseClampToEdge.spec.ts54
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleBias.spec.ts309
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleCompare.spec.ts308
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleCompareLevel.spec.ts268
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleGrad.spec.ts317
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleLevel.spec.ts282
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureStore.spec.ts168
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/trunc.spec.ts94
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16float.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16snorm.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16unorm.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4x8snorm.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4x8unorm.spec.ts62
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4xI8.spec.ts61
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4xU8.spec.ts61
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/workgroupUniformLoad.spec.ts122
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/overload_resolution.spec.ts268
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/precedence.spec.ts188
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/address_of_and_indirection.spec.ts243
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/arithmetic_negation.spec.ts114
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/bitwise_complement.spec.ts114
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/logical_negation.spec.ts114
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/extension/pointer_composite_access.spec.ts130
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/extension/readonly_and_readwrite_storage_textures.spec.ts48
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/alias_analysis.spec.ts545
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/restrictions.spec.ts313
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/break.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/break_if.spec.ts141
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/compound.spec.ts52
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/continuing.spec.ts185
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/diagnostic.spec.ts260
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/enable.spec.ts18
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/must_use.spec.ts63
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/pipeline_stage.spec.ts42
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/requires.spec.ts103
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/semicolon.spec.ts15
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/shadow_builtins.spec.ts995
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/statement_behavior.spec.ts143
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/binding.spec.ts14
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/builtins.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group.spec.ts14
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group_and_binding.spec.ts4
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/layout_constraints.spec.ts543
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/locations.spec.ts149
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/size.spec.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/alias.spec.ts122
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/array.spec.ts122
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/atomics.spec.ts145
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/matrix.spec.ts152
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/textures.spec.ts170
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/uniformity/uniformity.spec.ts209
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/binary_stream.ts10
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/check_contents.ts38
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/color_space_conversion.ts32
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/compare.ts32
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/constants.ts15
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/conversion.ts1688
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/device_pool.ts12
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/floating_point.ts601
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/math.ts465
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/pretty_diff_tables.ts37
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/shader.ts25
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/base.ts44
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/color_space_conversions.spec.ts108
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.spec.ts16
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.ts50
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_view.ts17
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texture_ok.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/configure.spec.ts5
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/getCurrentTexture.spec.ts75
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.ts155
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/canvas.spec.ts6
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/video.spec.ts86
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/external_texture/video.spec.ts349
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_bgra8unorm.https.html1
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba16float.https.html2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba8unorm.https.html1
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_copy.https.html2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_draw.https.html2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html2
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_image_rendering.https.html12
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/delay_get_texture.html.ts46
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/delay_get_texture.https.html10
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/ref/canvas_image_rendering-ref.html12
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/ref/delay_get_texture-ref.html17
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/util.ts430
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.spec.ts72
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.ts20
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker_launcher.ts59
748 files changed, 56555 insertions, 14044 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/common/framework/fixture.ts b/dom/webgpu/tests/cts/checkout/src/common/framework/fixture.ts
index 77875e047d..616023e20c 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/framework/fixture.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/framework/fixture.ts
@@ -15,7 +15,8 @@ export type TestParams = {
type DestroyableObject =
| { destroy(): void }
| { close(): void }
- | { getExtension(extensionName: 'WEBGL_lose_context'): WEBGL_lose_context };
+ | { getExtension(extensionName: 'WEBGL_lose_context'): WEBGL_lose_context }
+ | HTMLVideoElement;
export class SubcaseBatchState {
constructor(
@@ -124,8 +125,12 @@ export class Fixture<S extends SubcaseBatchState = SubcaseBatchState> {
if (WEBGL_lose_context) WEBGL_lose_context.loseContext();
} else if ('destroy' in o) {
o.destroy();
- } else {
+ } else if ('close' in o) {
o.close();
+ } else {
+ // HTMLVideoElement
+ o.src = '';
+ o.srcObject = null;
}
}
}
@@ -161,6 +166,14 @@ export class Fixture<S extends SubcaseBatchState = SubcaseBatchState> {
this.rec.debug(new Error(msg));
}
+ /**
+ * Log an info message.
+ * **Use sparingly. Use `debug()` instead if logs are only needed with debug logging enabled.**
+ */
+ info(msg: string): void {
+ this.rec.info(new Error(msg));
+ }
+
/** Throws an exception marking the subcase as skipped. */
skip(msg: string): never {
throw new SkipTestCase(msg);
diff --git a/dom/webgpu/tests/cts/checkout/src/common/framework/test_config.ts b/dom/webgpu/tests/cts/checkout/src/common/framework/test_config.ts
index 2575418299..e6624ae120 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/framework/test_config.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/framework/test_config.ts
@@ -1,4 +1,9 @@
export type TestConfig = {
+ /**
+ * Enable debug-level logs (normally logged via `Fixture.debug()`).
+ */
+ enableDebugLogs: boolean;
+
maxSubcasesInFlight: number;
testHeartbeatCallback: () => void;
noRaceWithRejectOnTimeout: boolean;
@@ -21,12 +26,25 @@ export type TestConfig = {
* Whether or not we're running in compatibility mode.
*/
compatibility: boolean;
+
+ /**
+ * Whether or not to request a fallback adapter.
+ */
+ forceFallbackAdapter: boolean;
+
+ /**
+ * Whether to enable the `logToWebSocket` function used for out-of-band test logging.
+ */
+ logToWebSocket: boolean;
};
export const globalTestConfig: TestConfig = {
+ enableDebugLogs: false,
maxSubcasesInFlight: 500,
testHeartbeatCallback: () => {},
noRaceWithRejectOnTimeout: false,
unrollConstEvalLoops: false,
compatibility: false,
+ forceFallbackAdapter: false,
+ logToWebSocket: false,
};
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/file_loader.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/file_loader.ts
index b5e1b1a446..aae4b87995 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/file_loader.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/file_loader.ts
@@ -73,8 +73,9 @@ export abstract class TestFileLoader extends EventTarget {
query: TestQuery,
{
subqueriesToExpand = [],
+ fullyExpandSubtrees = [],
maxChunkTime = Infinity,
- }: { subqueriesToExpand?: string[]; maxChunkTime?: number } = {}
+ }: { subqueriesToExpand?: string[]; fullyExpandSubtrees?: string[]; maxChunkTime?: number } = {}
): Promise<TestTree> {
const tree = await loadTreeForQuery(this, query, {
subqueriesToExpand: subqueriesToExpand.map(s => {
@@ -82,6 +83,7 @@ export abstract class TestFileLoader extends EventTarget {
assert(q.level >= 2, () => `subqueriesToExpand entries should not be multi-file:\n ${q}`);
return q;
}),
+ fullyExpandSubtrees: fullyExpandSubtrees.map(s => parseQuery(s)),
maxChunkTime,
});
this.dispatchEvent(new MessageEvent<void>('finish'));
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/logging/log_message.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/logging/log_message.ts
index ee006cdeb3..b01c08b56e 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/logging/log_message.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/logging/log_message.ts
@@ -1,19 +1,36 @@
import { ErrorWithExtra } from '../../util/util.js';
import { extractImportantStackTrace } from '../stack.js';
+import { LogMessageRawData } from './result.js';
+
export class LogMessageWithStack extends Error {
readonly extra: unknown;
private stackHiddenMessage: string | undefined = undefined;
- constructor(name: string, ex: Error | ErrorWithExtra) {
- super(ex.message);
+ /**
+ * Wrap an Error (which was created to capture the stack at that point) into a
+ * LogMessageWithStack (which has extra stuff for good log messages).
+ *
+ * The original `ex.name` is ignored. Inclued it in the `name` parameter if it
+ * needs to be preserved.
+ */
+ static wrapError(name: string, ex: Error | ErrorWithExtra) {
+ return new LogMessageWithStack({
+ name,
+ message: ex.message,
+ stackHiddenMessage: undefined,
+ stack: ex.stack,
+ extra: 'extra' in ex ? ex.extra : undefined,
+ });
+ }
- this.name = name;
- this.stack = ex.stack;
- if ('extra' in ex) {
- this.extra = ex.extra;
- }
+ constructor(o: LogMessageRawData) {
+ super(o.message);
+ this.name = o.name;
+ this.stackHiddenMessage = o.stackHiddenMessage;
+ this.stack = o.stack;
+ this.extra = o.extra;
}
/** Set a flag so the stack is not printed in toJSON(). */
@@ -21,6 +38,11 @@ export class LogMessageWithStack extends Error {
this.stackHiddenMessage ??= stackHiddenMessage;
}
+ /**
+ * Print the message for display.
+ *
+ * Note: This is toJSON instead of toString to make it easy to save logs using JSON.stringify.
+ */
toJSON(): string {
let m = this.name;
if (this.message) m += ': ' + this.message;
@@ -33,6 +55,21 @@ export class LogMessageWithStack extends Error {
}
return m;
}
+
+ /**
+ * Flatten the message for sending over a message channel.
+ *
+ * Note `extra` may get mangled by postMessage.
+ */
+ toRawData(): LogMessageRawData {
+ return {
+ name: this.name,
+ message: this.message,
+ stackHiddenMessage: this.stackHiddenMessage,
+ stack: this.stack,
+ extra: this.extra,
+ };
+ }
}
/**
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/logging/logger.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/logging/logger.ts
index e4526cff54..6b95f48b74 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/logging/logger.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/logging/logger.ts
@@ -1,3 +1,4 @@
+import { globalTestConfig } from '../../framework/test_config.js';
import { version } from '../version.js';
import { LiveTestCaseResult } from './result.js';
@@ -6,8 +7,6 @@ import { TestCaseRecorder } from './test_case_recorder.js';
export type LogResults = Map<string, LiveTestCaseResult>;
export class Logger {
- static globalDebugMode: boolean = false;
-
readonly overriddenDebugMode: boolean | undefined;
readonly results: LogResults = new Map();
@@ -19,7 +18,7 @@ export class Logger {
const result: LiveTestCaseResult = { status: 'running', timems: -1 };
this.results.set(name, result);
return [
- new TestCaseRecorder(result, this.overriddenDebugMode ?? Logger.globalDebugMode),
+ new TestCaseRecorder(result, this.overriddenDebugMode ?? globalTestConfig.enableDebugLogs),
result,
];
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/logging/result.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/logging/result.ts
index 3318e8c937..9968f3d359 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/logging/result.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/logging/result.ts
@@ -14,8 +14,24 @@ export interface LiveTestCaseResult extends TestCaseResult {
logs?: LogMessageWithStack[];
}
+/**
+ * Raw data for a test log message.
+ *
+ * This form is sendable over a message channel, except `extra` may get mangled.
+ */
+export interface LogMessageRawData {
+ name: string;
+ message: string;
+ stackHiddenMessage: string | undefined;
+ stack: string | undefined;
+ extra: unknown;
+}
+
+/**
+ * Test case results in a form sendable over a message channel.
+ *
+ * Note `extra` may get mangled by postMessage.
+ */
export interface TransferredTestCaseResult extends TestCaseResult {
- // When transferred from a worker, a LogMessageWithStack turns into a generic Error
- // (its prototype gets lost and replaced with Error).
- logs?: Error[];
+ logs?: LogMessageRawData[];
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/logging/test_case_recorder.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/logging/test_case_recorder.ts
index f5c3252b5c..78f625269e 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/logging/test_case_recorder.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/logging/test_case_recorder.ts
@@ -45,8 +45,6 @@ export class TestCaseRecorder {
private logs: LogMessageWithStack[] = [];
private logLinesAtCurrentSeverity = 0;
private debugging = false;
- /** Used to dedup log messages which have identical stacks. */
- private messagesForPreviouslySeenStacks = new Map<string, LogMessageWithStack>();
constructor(result: LiveTestCaseResult, debugging: boolean) {
this.result = result;
@@ -143,13 +141,15 @@ export class TestCaseRecorder {
this.skipped(ex);
return;
}
- this.logImpl(LogSeverity.ThrewException, 'EXCEPTION', ex);
+ // logImpl will discard the original error's ex.name. Preserve it here.
+ const name = ex instanceof Error ? `EXCEPTION: ${ex.name}` : 'EXCEPTION';
+ this.logImpl(LogSeverity.ThrewException, name, ex);
}
private logImpl(level: LogSeverity, name: string, baseException: unknown): void {
assert(baseException instanceof Error, 'test threw a non-Error object');
globalTestConfig.testHeartbeatCallback();
- const logMessage = new LogMessageWithStack(name, baseException);
+ const logMessage = LogMessageWithStack.wrapError(name, baseException);
// Final case status should be the "worst" of all log entries.
if (this.inSubCase) {
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/query/compare.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/query/compare.ts
index a9419b87c1..f49833f5a2 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/query/compare.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/query/compare.ts
@@ -58,7 +58,10 @@ function compareOneLevel(ordering: Ordering, aIsBig: boolean, bIsBig: boolean):
return Ordering.Unordered;
}
-function comparePaths(a: readonly string[], b: readonly string[]): Ordering {
+/**
+ * Compare two file paths, or file-local test paths, returning an Ordering between the two.
+ */
+export function comparePaths(a: readonly string[], b: readonly string[]): Ordering {
const shorter = Math.min(a.length, b.length);
for (let i = 0; i < shorter; ++i) {
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/query/parseQuery.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/query/parseQuery.ts
index 996835b0ec..0a9b355804 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/query/parseQuery.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/query/parseQuery.ts
@@ -17,12 +17,49 @@ import {
import { kBigSeparator, kWildcard, kPathSeparator, kParamSeparator } from './separators.js';
import { validQueryPart } from './validQueryPart.js';
-export function parseQuery(s: string): TestQuery {
+/**
+ * converts foo/bar/src/webgpu/this/that/file.spec.ts to webgpu:this,that,file,*
+ */
+function convertPathToQuery(path: string) {
+ // removes .spec.ts and splits by directory separators.
+ const parts = path.substring(0, path.length - 8).split(/\/|\\/g);
+ // Gets parts only after the last `src`. Example: returns ['webgpu', 'foo', 'bar', 'test']
+ // for ['Users', 'me', 'src', 'cts', 'src', 'webgpu', 'foo', 'bar', 'test']
+ const partsAfterSrc = parts.slice(parts.lastIndexOf('src') + 1);
+ const suite = partsAfterSrc.shift();
+ return `${suite}:${partsAfterSrc.join(',')},*`;
+}
+
+/**
+ * If a query looks like a path (ends in .spec.ts and has directory separators)
+ * then convert try to convert it to a query.
+ */
+function convertPathLikeToQuery(queryOrPath: string) {
+ return queryOrPath.endsWith('.spec.ts') &&
+ (queryOrPath.includes('/') || queryOrPath.includes('\\'))
+ ? convertPathToQuery(queryOrPath)
+ : queryOrPath;
+}
+
+/**
+ * Convert long suite names (the part before the first colon) to the
+ * shortest last word
+ * foo.bar.moo:test,subtest,foo -> moo:test,subtest,foo
+ */
+function shortenSuiteName(query: string) {
+ const parts = query.split(':');
+ // converts foo.bar.moo to moo
+ const suite = parts.shift()?.replace(/.*\.(\w+)$/, '$1');
+ return [suite, ...parts].join(':');
+}
+
+export function parseQuery(queryLike: string): TestQuery {
try {
- return parseQueryImpl(s);
+ const query = shortenSuiteName(convertPathLikeToQuery(queryLike));
+ return parseQueryImpl(query);
} catch (ex) {
if (ex instanceof Error) {
- ex.message += '\n on: ' + s;
+ ex.message += `\n on: ${queryLike}`;
}
throw ex;
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/query/query.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/query/query.ts
index 7c72a62f88..676ac46d38 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/query/query.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/query/query.ts
@@ -1,5 +1,5 @@
import { TestParams } from '../../framework/fixture.js';
-import { optionEnabled } from '../../runtime/helper/options.js';
+import { optionWorkerMode } from '../../runtime/helper/options.js';
import { assert, unreachable } from '../../util/util.js';
import { Expectation } from '../logging/result.js';
@@ -188,12 +188,12 @@ export function parseExpectationsForTestQuery(
assert(
expectationURL.pathname === wptURL.pathname,
`Invalid expectation path ${expectationURL.pathname}
-Expectation should be of the form path/to/cts.https.html?worker=0&q=suite:test_path:test_name:foo=1;bar=2;...
+Expectation should be of the form path/to/cts.https.html?debug=0&q=suite:test_path:test_name:foo=1;bar=2;...
`
);
const params = expectationURL.searchParams;
- if (optionEnabled('worker', params) !== optionEnabled('worker', wptURL.searchParams)) {
+ if (optionWorkerMode('worker', params) !== optionWorkerMode('worker', wptURL.searchParams)) {
continue;
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/test_group.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/test_group.ts
index 632a822ef1..e1d0cde12d 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/test_group.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/test_group.ts
@@ -34,7 +34,7 @@ import { validQueryPart } from '../internal/query/validQueryPart.js';
import { DeepReadonly } from '../util/types.js';
import { assert, unreachable } from '../util/util.js';
-import { logToWebsocket } from './websocket_logger.js';
+import { logToWebSocket } from './websocket_logger.js';
export type RunFn = (
rec: TestCaseRecorder,
@@ -294,9 +294,11 @@ class TestBuilder<S extends SubcaseBatchState, F extends Fixture> {
(this.description ? this.description + '\n\n' : '') + 'TODO: .unimplemented()';
this.isUnimplemented = true;
- this.testFn = () => {
+ // Use the beforeFn to skip the test, so we don't have to iterate the subcases.
+ this.beforeFn = () => {
throw new SkipTestCase('test unimplemented');
};
+ this.testFn = () => {};
}
/** Perform various validation/"lint" chenks. */
@@ -350,7 +352,7 @@ class TestBuilder<S extends SubcaseBatchState, F extends Fixture> {
const testcaseStringUnique = stringifyPublicParamsUniquely(params);
assert(
!seen.has(testcaseStringUnique),
- `Duplicate public test case+subcase params for test ${testPathString}: ${testcaseString}`
+ `Duplicate public test case+subcase params for test ${testPathString}: ${testcaseString} (${caseQuery})`
);
seen.add(testcaseStringUnique);
}
@@ -737,7 +739,7 @@ class RunCaseSpecific implements RunCase {
timems: rec.result.timems,
nonskippedSubcaseCount: rec.nonskippedSubcaseCount,
};
- logToWebsocket(JSON.stringify(msg));
+ logToWebSocket(JSON.stringify(msg));
}
}
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/test_suite_listing.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/test_suite_listing.ts
index 2d2b555366..c5a0e11448 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/test_suite_listing.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/test_suite_listing.ts
@@ -1,6 +1,6 @@
// A listing of all specs within a single suite. This is the (awaited) type of
// `groups` in '{cts,unittests}/listing.ts' and `listing` in the auto-generated
-// 'out/{cts,unittests}/listing.js' files (see tools/gen_listings).
+// 'out/{cts,unittests}/listing.js' files (see tools/gen_listings_and_webworkers).
export type TestSuiteListing = TestSuiteListingEntry[];
export type TestSuiteListingEntry = TestSuiteListingEntrySpec | TestSuiteListingEntryReadme;
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/tree.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/tree.ts
index 594837059c..f2fad59037 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/tree.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/tree.ts
@@ -286,8 +286,9 @@ export async function loadTreeForQuery(
queryToLoad: TestQuery,
{
subqueriesToExpand,
+ fullyExpandSubtrees = [],
maxChunkTime = Infinity,
- }: { subqueriesToExpand: TestQuery[]; maxChunkTime?: number }
+ }: { subqueriesToExpand: TestQuery[]; fullyExpandSubtrees?: TestQuery[]; maxChunkTime?: number }
): Promise<TestTree> {
const suite = queryToLoad.suite;
const specs = await loader.listing(suite);
@@ -303,6 +304,10 @@ export async function loadTreeForQuery(
// If toExpand == subquery, no expansion is needed (but it's still "seen").
if (ordering === Ordering.Equal) seenSubqueriesToExpand[i] = true;
return ordering !== Ordering.StrictSubset;
+ }) &&
+ fullyExpandSubtrees.every(toExpand => {
+ const ordering = compareQueries(toExpand, subquery);
+ return ordering === Ordering.Unordered;
});
// L0 = suite-level, e.g. suite:*
diff --git a/dom/webgpu/tests/cts/checkout/src/common/internal/websocket_logger.ts b/dom/webgpu/tests/cts/checkout/src/common/internal/websocket_logger.ts
index 30246df843..373378e7c2 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/internal/websocket_logger.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/internal/websocket_logger.ts
@@ -1,3 +1,5 @@
+import { globalTestConfig } from '../framework/test_config.js';
+
/**
* - 'uninitialized' means we haven't tried to connect yet
* - Promise means it's pending
@@ -8,12 +10,15 @@ let connection: Promise<WebSocket | 'failed'> | WebSocket | 'failed' | 'uninitia
'uninitialized';
/**
- * Log a string to a websocket at `localhost:59497`. See `tools/websocket-logger`.
+ * If the logToWebSocket option is enabled (?log_to_web_socket=1 in browser,
+ * --log-to-web-socket on command line, or enable it by default in options.ts),
+ * 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.
+ * This does nothing if a logToWebSocket is not enabled, or if a connection
+ * couldn't be established on the first call.
*/
-export function logToWebsocket(msg: string) {
- if (connection === 'failed') {
+export function logToWebSocket(msg: string) {
+ if (!globalTestConfig.logToWebSocket || connection === 'failed') {
return;
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/cmdline.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/cmdline.ts
index 44a73fb38b..2a00640f0e 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/runtime/cmdline.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/cmdline.ts
@@ -3,6 +3,7 @@
import * as fs from 'fs';
import { dataCache } from '../framework/data_cache.js';
+import { getResourcePath, setBaseResourcePath } from '../framework/resources.js';
import { globalTestConfig } from '../framework/test_config.js';
import { DefaultTestFileLoader } from '../internal/file_loader.js';
import { prettyPrintLog } from '../internal/logging/log_message.js';
@@ -37,6 +38,12 @@ Options:
return sys.exit(rc);
}
+if (!sys.existsSync('src/common/runtime/cmdline.ts')) {
+ console.log('Must be run from repository root');
+ usage(1);
+}
+setBaseResourcePath('out-node/resources');
+
// The interface that exposes creation of the GPU, and optional interface to code coverage.
interface GPUProviderModule {
// @returns a GPU with the given flags
@@ -60,12 +67,10 @@ Colors.enabled = false;
let verbose = false;
let emitCoverage = false;
let listMode: listModes = 'none';
-let debug = false;
let printJSON = false;
let quiet = false;
let loadWebGPUExpectations: Promise<unknown> | undefined = undefined;
let gpuProviderModule: GPUProviderModule | undefined = undefined;
-let dataPath: string | undefined = undefined;
const queries: string[] = [];
const gpuProviderFlags: string[] = [];
@@ -83,9 +88,7 @@ for (let i = 0; i < sys.args.length; ++i) {
} else if (a === '--list-unimplemented') {
listMode = 'unimplemented';
} else if (a === '--debug') {
- debug = true;
- } else if (a === '--data') {
- dataPath = sys.args[++i];
+ globalTestConfig.enableDebugLogs = true;
} else if (a === '--print-json') {
printJSON = true;
} else if (a === '--expectations') {
@@ -102,6 +105,10 @@ for (let i = 0; i < sys.args.length; ++i) {
globalTestConfig.unrollConstEvalLoops = true;
} else if (a === '--compat') {
globalTestConfig.compatibility = true;
+ } else if (a === '--force-fallback-adapter') {
+ globalTestConfig.forceFallbackAdapter = true;
+ } else if (a === '--log-to-websocket') {
+ globalTestConfig.logToWebSocket = true;
} else {
console.log('unrecognized flag: ', a);
usage(1);
@@ -113,9 +120,12 @@ for (let i = 0; i < sys.args.length; ++i) {
let codeCoverage: CodeCoverageProvider | undefined = undefined;
-if (globalTestConfig.compatibility) {
+if (globalTestConfig.compatibility || globalTestConfig.forceFallbackAdapter) {
// MAINTENANCE_TODO: remove the cast once compatibilityMode is officially added
- setDefaultRequestAdapterOptions({ compatibilityMode: true } as GPURequestAdapterOptions);
+ setDefaultRequestAdapterOptions({
+ compatibilityMode: globalTestConfig.compatibility,
+ forceFallbackAdapter: globalTestConfig.forceFallbackAdapter,
+ } as GPURequestAdapterOptions);
}
if (gpuProviderModule) {
@@ -132,21 +142,20 @@ Did you remember to build with code coverage instrumentation enabled?`
}
}
-if (dataPath !== undefined) {
- dataCache.setStore({
- load: (path: string) => {
- return new Promise<Uint8Array>((resolve, reject) => {
- fs.readFile(`${dataPath}/${path}`, (err, data) => {
- if (err !== null) {
- reject(err.message);
- } else {
- resolve(data);
- }
- });
+dataCache.setStore({
+ load: (path: string) => {
+ return new Promise<Uint8Array>((resolve, reject) => {
+ fs.readFile(getResourcePath(`cache/${path}`), (err, data) => {
+ if (err !== null) {
+ reject(err.message);
+ } else {
+ resolve(data);
+ }
});
- },
- });
-}
+ });
+ },
+});
+
if (verbose) {
dataCache.setDebugLogger(console.log);
}
@@ -166,7 +175,6 @@ if (queries.length === 0) {
filterQuery
);
- Logger.globalDebugMode = debug;
const log = new Logger();
const failed: Array<[string, LiveTestCaseResult]> = [];
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/options.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/options.ts
index 38974b803f..4a82c7d292 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/options.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/options.ts
@@ -1,3 +1,5 @@
+import { unreachable } from '../../util/util.js';
+
let windowURL: URL | undefined = undefined;
function getWindowURL() {
if (windowURL === undefined) {
@@ -6,6 +8,7 @@ function getWindowURL() {
return windowURL;
}
+/** Parse a runner option that is always boolean-typed. False if missing or '0'. */
export function optionEnabled(
opt: string,
searchParams: URLSearchParams = getWindowURL().searchParams
@@ -14,30 +17,55 @@ export function optionEnabled(
return val !== null && val !== '0';
}
+/** Parse a runner option that is string-typed. If the option is missing, returns `null`. */
export function optionString(
opt: string,
searchParams: URLSearchParams = getWindowURL().searchParams
-): string {
- return searchParams.get(opt) || '';
+): string | null {
+ return searchParams.get(opt);
+}
+
+/** Runtime modes for running tests in different types of workers. */
+export type WorkerMode = 'dedicated' | 'service' | 'shared';
+/** Parse a runner option for different worker modes (as in `?worker=shared`). Null if no worker. */
+export function optionWorkerMode(
+ opt: string,
+ searchParams: URLSearchParams = getWindowURL().searchParams
+): WorkerMode | null {
+ const value = searchParams.get(opt);
+ if (value === null || value === '0') {
+ return null;
+ } else if (value === 'service') {
+ return 'service';
+ } else if (value === 'shared') {
+ return 'shared';
+ } else if (value === '' || value === '1' || value === 'dedicated') {
+ return 'dedicated';
+ }
+ unreachable('invalid worker= option value');
}
/**
* The possible options for the tests.
*/
export interface CTSOptions {
- worker: boolean;
+ worker: WorkerMode | null;
debug: boolean;
compatibility: boolean;
+ forceFallbackAdapter: boolean;
unrollConstEvalLoops: boolean;
- powerPreference?: GPUPowerPreference | '';
+ powerPreference: GPUPowerPreference | null;
+ logToWebSocket: boolean;
}
export const kDefaultCTSOptions: CTSOptions = {
- worker: false,
+ worker: null,
debug: true,
compatibility: false,
+ forceFallbackAdapter: false,
unrollConstEvalLoops: false,
- powerPreference: '',
+ powerPreference: null,
+ logToWebSocket: false,
};
/**
@@ -45,8 +73,8 @@ export const kDefaultCTSOptions: CTSOptions = {
*/
export interface OptionInfo {
description: string;
- parser?: (key: string, searchParams?: URLSearchParams) => boolean | string;
- selectValueDescriptions?: { value: string; description: string }[];
+ parser?: (key: string, searchParams?: URLSearchParams) => boolean | string | null;
+ selectValueDescriptions?: { value: string | null; description: string }[];
}
/**
@@ -59,19 +87,30 @@ export type OptionsInfos<Type> = Record<keyof Type, OptionInfo>;
* Options to the CTS.
*/
export const kCTSOptionsInfo: OptionsInfos<CTSOptions> = {
- worker: { description: 'run in a worker' },
+ worker: {
+ description: 'run in a worker',
+ parser: optionWorkerMode,
+ selectValueDescriptions: [
+ { value: null, description: 'no worker' },
+ { value: 'dedicated', description: 'dedicated worker' },
+ { value: 'shared', description: 'shared worker' },
+ { value: 'service', description: 'service worker' },
+ ],
+ },
debug: { description: 'show more info' },
compatibility: { description: 'run in compatibility mode' },
+ forceFallbackAdapter: { description: 'pass forceFallbackAdapter: true to requestAdapter' },
unrollConstEvalLoops: { description: 'unroll const eval loops in WGSL' },
powerPreference: {
description: 'set default powerPreference for some tests',
parser: optionString,
selectValueDescriptions: [
- { value: '', description: 'default' },
+ { value: null, description: 'default' },
{ value: 'low-power', description: 'low-power' },
{ value: 'high-performance', description: 'high-performance' },
],
},
+ logToWebSocket: { description: 'send some logs to ws://localhost:59497/' },
};
/**
@@ -95,7 +134,7 @@ function getOptionsInfoFromSearchString<Type extends CTSOptions>(
searchString: string
): Type {
const searchParams = new URLSearchParams(searchString);
- const optionValues: Record<string, boolean | string> = {};
+ const optionValues: Record<string, boolean | string | null> = {};
for (const [optionName, info] of Object.entries(optionsInfos)) {
const parser = info.parser || optionEnabled;
optionValues[optionName] = parser(camelCaseToSnakeCase(optionName), searchParams);
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker-worker.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker-worker.ts
index e8d187ea7e..ebc206c3b2 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker-worker.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker-worker.ts
@@ -1,15 +1,11 @@
import { setBaseResourcePath } from '../../framework/resources.js';
-import { globalTestConfig } from '../../framework/test_config.js';
import { DefaultTestFileLoader } from '../../internal/file_loader.js';
-import { Logger } from '../../internal/logging/logger.js';
import { parseQuery } from '../../internal/query/parseQuery.js';
-import { TestQueryWithExpectation } from '../../internal/query/query.js';
-import { setDefaultRequestAdapterOptions } from '../../util/navigator_gpu.js';
import { assert } from '../../util/util.js';
-import { CTSOptions } from './options.js';
+import { setupWorkerEnvironment, WorkerTestRunRequest } from './utils_worker.js';
-// Should be DedicatedWorkerGlobalScope, but importing lib "webworker" conflicts with lib "dom".
+// Should be WorkerGlobalScope, but importing lib "webworker" conflicts with lib "dom".
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
declare const self: any;
@@ -17,25 +13,10 @@ const loader = new DefaultTestFileLoader();
setBaseResourcePath('../../../resources');
-self.onmessage = async (ev: MessageEvent) => {
- const query: string = ev.data.query;
- const expectations: TestQueryWithExpectation[] = ev.data.expectations;
- const ctsOptions: CTSOptions = ev.data.ctsOptions;
+async function reportTestResults(this: MessagePort | Worker, ev: MessageEvent) {
+ const { query, expectations, ctsOptions } = ev.data as WorkerTestRunRequest;
- const { debug, unrollConstEvalLoops, powerPreference, compatibility } = ctsOptions;
- globalTestConfig.unrollConstEvalLoops = unrollConstEvalLoops;
- globalTestConfig.compatibility = compatibility;
-
- Logger.globalDebugMode = debug;
- const log = new Logger();
-
- if (powerPreference || compatibility) {
- setDefaultRequestAdapterOptions({
- ...(powerPreference && { powerPreference }),
- // MAINTENANCE_TODO: Change this to whatever the option ends up being
- ...(compatibility && { compatibilityMode: true }),
- });
- }
+ const log = setupWorkerEnvironment(ctsOptions);
const testcases = Array.from(await loader.loadCases(parseQuery(query)));
assert(testcases.length === 1, 'worker query resulted in != 1 cases');
@@ -44,5 +25,23 @@ self.onmessage = async (ev: MessageEvent) => {
const [rec, result] = log.record(testcase.query.toString());
await testcase.run(rec, expectations);
- self.postMessage({ query, result });
+ this.postMessage({
+ query,
+ result: {
+ ...result,
+ logs: result.logs?.map(l => l.toRawData()),
+ },
+ });
+}
+
+self.onmessage = (ev: MessageEvent) => {
+ void reportTestResults.call(ev.source || self, ev);
+};
+
+self.onconnect = (event: MessageEvent) => {
+ const port = event.ports[0];
+
+ port.onmessage = (ev: MessageEvent) => {
+ void reportTestResults.call(port, ev);
+ };
};
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker.ts
index 9bbcab0946..f9a44bb7bc 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker.ts
@@ -2,48 +2,190 @@ import { LogMessageWithStack } from '../../internal/logging/log_message.js';
import { TransferredTestCaseResult, LiveTestCaseResult } from '../../internal/logging/result.js';
import { TestCaseRecorder } from '../../internal/logging/test_case_recorder.js';
import { TestQueryWithExpectation } from '../../internal/query/query.js';
+import { timeout } from '../../util/timeout.js';
+import { assert } from '../../util/util.js';
-import { CTSOptions, kDefaultCTSOptions } from './options.js';
+import { CTSOptions, WorkerMode, kDefaultCTSOptions } from './options.js';
+import { WorkerTestRunRequest } from './utils_worker.js';
-export class TestWorker {
- private readonly ctsOptions: CTSOptions;
- private readonly worker: Worker;
- private readonly resolvers = new Map<string, (result: LiveTestCaseResult) => void>();
+/** Query all currently-registered service workers, and unregister them. */
+function unregisterAllServiceWorkers() {
+ void navigator.serviceWorker.getRegistrations().then(registrations => {
+ for (const registration of registrations) {
+ void registration.unregister();
+ }
+ });
+}
- constructor(ctsOptions?: CTSOptions) {
- this.ctsOptions = { ...(ctsOptions || kDefaultCTSOptions), ...{ worker: true } };
- const selfPath = import.meta.url;
- const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/'));
- const workerPath = selfPathDir + '/test_worker-worker.js';
- this.worker = new Worker(workerPath, { type: 'module' });
- this.worker.onmessage = ev => {
- const query: string = ev.data.query;
- const result: TransferredTestCaseResult = ev.data.result;
- if (result.logs) {
- for (const l of result.logs) {
- Object.setPrototypeOf(l, LogMessageWithStack.prototype);
- }
- }
- this.resolvers.get(query)!(result as LiveTestCaseResult);
+// NOTE: This code runs on startup for any runtime with worker support. Here, we use that chance to
+// delete any leaked service workers, and register to clean up after ourselves at shutdown.
+unregisterAllServiceWorkers();
+window.addEventListener('beforeunload', () => {
+ unregisterAllServiceWorkers();
+});
+
+abstract class TestBaseWorker {
+ protected readonly ctsOptions: CTSOptions;
+ protected readonly resolvers = new Map<string, (result: LiveTestCaseResult) => void>();
+
+ constructor(worker: WorkerMode, ctsOptions?: CTSOptions) {
+ this.ctsOptions = { ...(ctsOptions || kDefaultCTSOptions), ...{ worker } };
+ }
- // MAINTENANCE_TODO(kainino0x): update the Logger with this result (or don't have a logger and
- // update the entire results JSON somehow at some point).
+ onmessage(ev: MessageEvent) {
+ const query: string = ev.data.query;
+ const transferredResult: TransferredTestCaseResult = ev.data.result;
+
+ const result: LiveTestCaseResult = {
+ status: transferredResult.status,
+ timems: transferredResult.timems,
+ logs: transferredResult.logs?.map(l => new LogMessageWithStack(l)),
};
+
+ this.resolvers.get(query)!(result);
+ this.resolvers.delete(query);
+
+ // MAINTENANCE_TODO(kainino0x): update the Logger with this result (or don't have a logger and
+ // update the entire results JSON somehow at some point).
}
- async run(
- rec: TestCaseRecorder,
+ makeRequestAndRecordResult(
+ target: MessagePort | Worker | ServiceWorker,
query: string,
- expectations: TestQueryWithExpectation[] = []
- ): Promise<void> {
- this.worker.postMessage({
+ expectations: TestQueryWithExpectation[]
+ ): Promise<LiveTestCaseResult> {
+ const request: WorkerTestRunRequest = {
query,
expectations,
ctsOptions: this.ctsOptions,
- });
- const workerResult = await new Promise<LiveTestCaseResult>(resolve => {
+ };
+ target.postMessage(request);
+
+ return new Promise<LiveTestCaseResult>(resolve => {
+ assert(!this.resolvers.has(query), "can't request same query twice simultaneously");
this.resolvers.set(query, resolve);
});
- rec.injectResult(workerResult);
+ }
+
+ async run(
+ rec: TestCaseRecorder,
+ query: string,
+ expectations: TestQueryWithExpectation[] = []
+ ): Promise<void> {
+ try {
+ rec.injectResult(await this.runImpl(query, expectations));
+ } catch (ex) {
+ rec.start();
+ rec.threw(ex);
+ rec.finish();
+ }
+ }
+
+ protected abstract runImpl(
+ query: string,
+ expectations: TestQueryWithExpectation[]
+ ): Promise<LiveTestCaseResult>;
+}
+
+export class TestDedicatedWorker extends TestBaseWorker {
+ private readonly worker: Worker | Error;
+
+ constructor(ctsOptions?: CTSOptions) {
+ super('dedicated', ctsOptions);
+ try {
+ if (typeof Worker === 'undefined') {
+ throw new Error('Dedicated Workers not available');
+ }
+
+ const selfPath = import.meta.url;
+ const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/'));
+ const workerPath = selfPathDir + '/test_worker-worker.js';
+ this.worker = new Worker(workerPath, { type: 'module' });
+ this.worker.onmessage = ev => this.onmessage(ev);
+ } catch (ex) {
+ assert(ex instanceof Error);
+ // Save the exception to re-throw in runImpl().
+ this.worker = ex;
+ }
+ }
+
+ override runImpl(query: string, expectations: TestQueryWithExpectation[] = []) {
+ if (this.worker instanceof Worker) {
+ return this.makeRequestAndRecordResult(this.worker, query, expectations);
+ } else {
+ throw this.worker;
+ }
+ }
+}
+
+/** @deprecated Use TestDedicatedWorker instead. */
+export class TestWorker extends TestDedicatedWorker {}
+
+export class TestSharedWorker extends TestBaseWorker {
+ /** MessagePort to the SharedWorker, or an Error if it couldn't be initialized. */
+ private readonly port: MessagePort | Error;
+
+ constructor(ctsOptions?: CTSOptions) {
+ super('shared', ctsOptions);
+ try {
+ if (typeof SharedWorker === 'undefined') {
+ throw new Error('Shared Workers not available');
+ }
+
+ const selfPath = import.meta.url;
+ const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/'));
+ const workerPath = selfPathDir + '/test_worker-worker.js';
+ const worker = new SharedWorker(workerPath, { type: 'module' });
+ this.port = worker.port;
+ this.port.start();
+ this.port.onmessage = ev => this.onmessage(ev);
+ } catch (ex) {
+ assert(ex instanceof Error);
+ // Save the exception to re-throw in runImpl().
+ this.port = ex;
+ }
+ }
+
+ override runImpl(query: string, expectations: TestQueryWithExpectation[] = []) {
+ if (this.port instanceof MessagePort) {
+ return this.makeRequestAndRecordResult(this.port, query, expectations);
+ } else {
+ throw this.port;
+ }
+ }
+}
+
+export class TestServiceWorker extends TestBaseWorker {
+ constructor(ctsOptions?: CTSOptions) {
+ super('service', ctsOptions);
+ }
+
+ override async runImpl(query: string, expectations: TestQueryWithExpectation[] = []) {
+ if (!('serviceWorker' in navigator)) {
+ throw new Error('Service Workers not available');
+ }
+ const [suite, name] = query.split(':', 2);
+ const fileName = name.split(',').join('/');
+
+ const selfPath = import.meta.url;
+ const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/'));
+ // Construct the path to the worker file, then use URL to resolve the `../` components.
+ const serviceWorkerURL = new URL(
+ `${selfPathDir}/../../../${suite}/webworker/${fileName}.worker.js`
+ ).toString();
+
+ // If a registration already exists for this path, it will be ignored.
+ const registration = await navigator.serviceWorker.register(serviceWorkerURL, {
+ type: 'module',
+ });
+ // Make sure the registration we just requested is active. (We don't worry about it being
+ // outdated from a previous page load, because we wipe all service workers on shutdown/startup.)
+ while (!registration.active || registration.active.scriptURL !== serviceWorkerURL) {
+ await new Promise(resolve => timeout(resolve, 0));
+ }
+ const serviceWorker = registration.active;
+
+ navigator.serviceWorker.onmessage = ev => this.onmessage(ev);
+ return this.makeRequestAndRecordResult(serviceWorker, query, expectations);
}
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/utils_worker.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/utils_worker.ts
new file mode 100644
index 0000000000..13880635bc
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/utils_worker.ts
@@ -0,0 +1,35 @@
+import { globalTestConfig } from '../../framework/test_config.js';
+import { Logger } from '../../internal/logging/logger.js';
+import { TestQueryWithExpectation } from '../../internal/query/query.js';
+import { setDefaultRequestAdapterOptions } from '../../util/navigator_gpu.js';
+
+import { CTSOptions } from './options.js';
+
+export interface WorkerTestRunRequest {
+ query: string;
+ expectations: TestQueryWithExpectation[];
+ ctsOptions: CTSOptions;
+}
+
+/**
+ * Set config environment for workers with ctsOptions and return a Logger.
+ */
+export function setupWorkerEnvironment(ctsOptions: CTSOptions): Logger {
+ const { powerPreference, compatibility } = ctsOptions;
+ globalTestConfig.enableDebugLogs = ctsOptions.debug;
+ globalTestConfig.unrollConstEvalLoops = ctsOptions.unrollConstEvalLoops;
+ globalTestConfig.compatibility = compatibility;
+ globalTestConfig.logToWebSocket = ctsOptions.logToWebSocket;
+
+ const log = new Logger();
+
+ if (powerPreference || compatibility) {
+ setDefaultRequestAdapterOptions({
+ ...(powerPreference && { powerPreference }),
+ // MAINTENANCE_TODO: Change this to whatever the option ends up being
+ ...(compatibility && { compatibilityMode: true }),
+ });
+ }
+
+ return log;
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/wrap_for_worker.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/wrap_for_worker.ts
new file mode 100644
index 0000000000..5f600fe89d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/wrap_for_worker.ts
@@ -0,0 +1,54 @@
+import { Fixture } from '../../framework/fixture';
+import { LogMessageWithStack } from '../../internal/logging/log_message.js';
+import { comparePaths, comparePublicParamsPaths, Ordering } from '../../internal/query/compare.js';
+import { parseQuery } from '../../internal/query/parseQuery.js';
+import { TestQuerySingleCase } from '../../internal/query/query.js';
+import { TestGroup } from '../../internal/test_group.js';
+import { assert } from '../../util/util.js';
+
+import { setupWorkerEnvironment, WorkerTestRunRequest } from './utils_worker.js';
+
+/**
+ * Sets up the currently running Web Worker to wrap the TestGroup object `g`.
+ * `g` is the `g` exported from a `.spec.ts` file: a TestGroupBuilder<F> interface,
+ * which underneath is actually a TestGroup<F> object.
+ *
+ * This is used in the generated `.worker.js` files that are generated to use as service workers.
+ */
+export function wrapTestGroupForWorker(g: TestGroup<Fixture>) {
+ self.onmessage = async (ev: MessageEvent) => {
+ const { query, expectations, ctsOptions } = ev.data as WorkerTestRunRequest;
+ try {
+ const log = setupWorkerEnvironment(ctsOptions);
+
+ const testQuery = parseQuery(query);
+ assert(testQuery instanceof TestQuerySingleCase);
+ let testcase = null;
+ for (const t of g.iterate()) {
+ if (comparePaths(t.testPath, testQuery.testPathParts) !== Ordering.Equal) {
+ continue;
+ }
+ for (const c of t.iterate(testQuery.params)) {
+ if (comparePublicParamsPaths(c.id.params, testQuery.params) === Ordering.Equal) {
+ testcase = c;
+ }
+ }
+ }
+ assert(!!testcase, 'testcase not found');
+ const [rec, result] = log.record(query);
+ await testcase.run(rec, testQuery, expectations);
+
+ ev.source?.postMessage({ query, result });
+ } catch (thrown) {
+ const ex = thrown instanceof Error ? thrown : new Error(`${thrown}`);
+ ev.source?.postMessage({
+ query,
+ result: {
+ status: 'fail',
+ timems: 0,
+ logs: [LogMessageWithStack.wrapError('INTERNAL', ex)],
+ },
+ });
+ }
+ };
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/server.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/server.ts
index 8310784e3a..3999b285ba 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/runtime/server.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/server.ts
@@ -5,6 +5,7 @@ import * as http from 'http';
import { AddressInfo } from 'net';
import { dataCache } from '../framework/data_cache.js';
+import { getResourcePath, setBaseResourcePath } from '../framework/resources.js';
import { globalTestConfig } from '../framework/test_config.js';
import { DefaultTestFileLoader } from '../internal/file_loader.js';
import { prettyPrintLog } from '../internal/logging/log_message.js';
@@ -20,21 +21,23 @@ import sys from './helper/sys.js';
function usage(rc: number): never {
console.log(`Usage:
- tools/run_${sys.type} [OPTIONS...]
+ tools/server [OPTIONS...]
Options:
--colors Enable ANSI colors in output.
--compat Run tests in compatibility mode.
--coverage Add coverage data to each result.
- --data Path to the data cache directory.
--verbose Print result/log of every test as it runs.
+ --debug Include debug messages in logging.
--gpu-provider Path to node module that provides the GPU implementation.
--gpu-provider-flag Flag to set on the gpu-provider as <flag>=<value>
--unroll-const-eval-loops Unrolls loops in constant-evaluation shader execution tests
--u Flag to set on the gpu-provider as <flag>=<value>
Provides an HTTP server used for running tests via an HTTP RPC interface
-To run a test, perform an HTTP GET or POST at the URL:
- http://localhost:port/run?<test-name>
+First, load some tree or subtree of tests:
+ http://localhost:port/load?unittests:basic:*
+To run a single test case, perform an HTTP GET or POST at the URL:
+ http://localhost:port/run?unittests:basic:test,sync
To shutdown the server perform an HTTP GET or POST at the URL:
http://localhost:port/terminate
`);
@@ -46,6 +49,8 @@ interface RunResult {
status: Status;
// Any additional messages printed
message: string;
+ // The time it took to execute the test
+ durationMS: number;
// Code coverage data, if the server was started with `--coverage`
// This data is opaque (implementation defined).
coverageData?: string;
@@ -71,13 +76,13 @@ if (!sys.existsSync('src/common/runtime/cmdline.ts')) {
console.log('Must be run from repository root');
usage(1);
}
+setBaseResourcePath('out-node/resources');
Colors.enabled = false;
let emitCoverage = false;
let verbose = false;
let gpuProviderModule: GPUProviderModule | undefined = undefined;
-let dataPath: string | undefined = undefined;
const gpuProviderFlags: string[] = [];
for (let i = 0; i < sys.args.length; ++i) {
@@ -89,13 +94,17 @@ for (let i = 0; i < sys.args.length; ++i) {
globalTestConfig.compatibility = true;
} else if (a === '--coverage') {
emitCoverage = true;
- } else if (a === '--data') {
- dataPath = sys.args[++i];
+ } else if (a === '--force-fallback-adapter') {
+ globalTestConfig.forceFallbackAdapter = true;
+ } else if (a === '--log-to-websocket') {
+ globalTestConfig.logToWebSocket = true;
} else if (a === '--gpu-provider') {
const modulePath = sys.args[++i];
gpuProviderModule = require(modulePath);
} else if (a === '--gpu-provider-flag') {
gpuProviderFlags.push(sys.args[++i]);
+ } else if (a === '--debug') {
+ globalTestConfig.enableDebugLogs = true;
} else if (a === '--unroll-const-eval-loops') {
globalTestConfig.unrollConstEvalLoops = true;
} else if (a === '--help') {
@@ -110,9 +119,12 @@ for (let i = 0; i < sys.args.length; ++i) {
let codeCoverage: CodeCoverageProvider | undefined = undefined;
-if (globalTestConfig.compatibility) {
+if (globalTestConfig.compatibility || globalTestConfig.forceFallbackAdapter) {
// MAINTENANCE_TODO: remove the cast once compatibilityMode is officially added
- setDefaultRequestAdapterOptions({ compatibilityMode: true } as GPURequestAdapterOptions);
+ setDefaultRequestAdapterOptions({
+ compatibilityMode: globalTestConfig.compatibility,
+ forceFallbackAdapter: globalTestConfig.forceFallbackAdapter,
+ } as GPURequestAdapterOptions);
}
if (gpuProviderModule) {
@@ -130,28 +142,26 @@ Did you remember to build with code coverage instrumentation enabled?`
}
}
-if (dataPath !== undefined) {
- dataCache.setStore({
- load: (path: string) => {
- return new Promise<Uint8Array>((resolve, reject) => {
- fs.readFile(`${dataPath}/${path}`, (err, data) => {
- if (err !== null) {
- reject(err.message);
- } else {
- resolve(data);
- }
- });
+dataCache.setStore({
+ load: (path: string) => {
+ return new Promise<Uint8Array>((resolve, reject) => {
+ fs.readFile(getResourcePath(`cache/${path}`), (err, data) => {
+ if (err !== null) {
+ reject(err.message);
+ } else {
+ resolve(data);
+ }
});
- },
- });
-}
+ });
+ },
+});
+
if (verbose) {
dataCache.setDebugLogger(console.log);
}
// eslint-disable-next-line @typescript-eslint/require-await
(async () => {
- Logger.globalDebugMode = verbose;
const log = new Logger();
const testcases = new Map<string, TestTreeLeaf>();
@@ -198,14 +208,16 @@ if (verbose) {
if (codeCoverage !== undefined) {
codeCoverage.begin();
}
+ const start = performance.now();
const result = await runTestcase(testcase);
+ const durationMS = performance.now() - start;
const coverageData = codeCoverage !== undefined ? codeCoverage.end() : undefined;
let message = '';
if (result.logs !== undefined) {
message = result.logs.map(log => prettyPrintLog(log)).join('\n');
}
const status = result.status;
- const res: RunResult = { status, message, coverageData };
+ const res: RunResult = { status, message, durationMS, coverageData };
response.statusCode = 200;
response.end(JSON.stringify(res));
} else {
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/standalone.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/standalone.ts
index 0376f92dda..dc75e6fd01 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/runtime/standalone.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/standalone.ts
@@ -2,7 +2,7 @@
/* eslint no-console: "off" */
import { dataCache } from '../framework/data_cache.js';
-import { setBaseResourcePath } from '../framework/resources.js';
+import { getResourcePath, setBaseResourcePath } from '../framework/resources.js';
import { globalTestConfig } from '../framework/test_config.js';
import { DefaultTestFileLoader } from '../internal/file_loader.js';
import { Logger } from '../internal/logging/logger.js';
@@ -21,7 +21,7 @@ import {
OptionsInfos,
camelCaseToSnakeCase,
} from './helper/options.js';
-import { TestWorker } from './helper/test_worker.js';
+import { TestDedicatedWorker, TestSharedWorker, TestServiceWorker } from './helper/test_worker.js';
const rootQuerySpec = 'webgpu:*';
let promptBeforeReload = false;
@@ -47,16 +47,26 @@ const { queries: qs, options } = parseSearchParamLikeWithOptions(
kStandaloneOptionsInfos,
window.location.search || rootQuerySpec
);
-const { runnow, debug, unrollConstEvalLoops, powerPreference, compatibility } = options;
-globalTestConfig.unrollConstEvalLoops = unrollConstEvalLoops;
+const { runnow, powerPreference, compatibility, forceFallbackAdapter } = options;
+globalTestConfig.enableDebugLogs = options.debug;
+globalTestConfig.unrollConstEvalLoops = options.unrollConstEvalLoops;
globalTestConfig.compatibility = compatibility;
+globalTestConfig.logToWebSocket = options.logToWebSocket;
-Logger.globalDebugMode = debug;
const logger = new Logger();
setBaseResourcePath('../out/resources');
-const worker = options.worker ? new TestWorker(options) : undefined;
+const testWorker =
+ options.worker === null
+ ? null
+ : options.worker === 'dedicated'
+ ? new TestDedicatedWorker(options)
+ : options.worker === 'shared'
+ ? new TestSharedWorker(options)
+ : options.worker === 'service'
+ ? new TestServiceWorker(options)
+ : unreachable();
const autoCloseOnPass = document.getElementById('autoCloseOnPass') as HTMLInputElement;
const resultsVis = document.getElementById('resultsVis')!;
@@ -70,17 +80,18 @@ stopButtonElem.addEventListener('click', () => {
stopRequested = true;
});
-if (powerPreference || compatibility) {
+if (powerPreference || compatibility || forceFallbackAdapter) {
setDefaultRequestAdapterOptions({
...(powerPreference && { powerPreference }),
// MAINTENANCE_TODO: Change this to whatever the option ends up being
...(compatibility && { compatibilityMode: true }),
+ ...(forceFallbackAdapter && { forceFallbackAdapter: true }),
});
}
dataCache.setStore({
load: async (path: string) => {
- const response = await fetch(`data/${path}`);
+ const response = await fetch(getResourcePath(`cache/${path}`));
if (!response.ok) {
return Promise.reject(response.statusText);
}
@@ -168,8 +179,8 @@ function makeCaseHTML(t: TestTreeLeaf): VisualizedSubtree {
const [rec, res] = logger.record(name);
caseResult = res;
- if (worker) {
- await worker.run(rec, name);
+ if (testWorker) {
+ await testWorker.run(rec, name);
} else {
await t.run(rec);
}
@@ -223,6 +234,12 @@ function makeCaseHTML(t: TestTreeLeaf): VisualizedSubtree {
if (caseResult.logs) {
caselogs.empty();
+ // Show exceptions at the top since they are often unexpected can point out an error in the test itself vs the WebGPU implementation.
+ caseResult.logs
+ .filter(l => l.name === 'EXCEPTION')
+ .forEach(l => {
+ $('<pre>').addClass('testcaselogtext').text(l.toJSON()).appendTo(caselogs);
+ });
for (const l of caseResult.logs) {
const caselog = $('<div>').addClass('testcaselog').appendTo(caselogs);
$('<button>')
@@ -500,13 +517,11 @@ function makeTreeNodeHeaderHTML(
// Collapse s:f:t:* or s:f:t:c by default.
let lastQueryLevelToExpand: TestQueryLevel = 2;
-type ParamValue = string | undefined | null | boolean | string[];
-
/**
* Takes an array of string, ParamValue and returns an array of pairs
* of [key, value] where value is a string. Converts boolean to '0' or '1'.
*/
-function keyValueToPairs([k, v]: [string, ParamValue]): [string, string][] {
+function keyValueToPairs([k, v]: [string, boolean | string | null]): [string, string][] {
const key = camelCaseToSnakeCase(k);
if (typeof v === 'boolean') {
return [[key, v ? '1' : '0']];
@@ -527,9 +542,9 @@ function keyValueToPairs([k, v]: [string, ParamValue]): [string, string][] {
* @param params Some object with key value pairs.
* @returns a search string.
*/
-function prepareParams(params: Record<string, ParamValue>): string {
+function prepareParams(params: Record<string, boolean | string | null>): string {
const pairsArrays = Object.entries(params)
- .filter(([, v]) => !!v)
+ .filter(([, v]) => !(v === false || v === null || v === '0'))
.map(keyValueToPairs);
const pairs = pairsArrays.flat();
return new URLSearchParams(pairs).toString();
@@ -537,7 +552,7 @@ function prepareParams(params: Record<string, ParamValue>): string {
// This is just a cast in one place.
export function optionsToRecord(options: CTSOptions) {
- return options as unknown as Record<string, boolean | string>;
+ return options as unknown as Record<string, boolean | string | null>;
}
/**
@@ -597,15 +612,15 @@ void (async () => {
};
const createSelect = (optionName: string, info: OptionInfo) => {
- const select = $('<select>').on('change', function () {
- optionValues[optionName] = (this as HTMLInputElement).value;
+ const select = $('<select>').on('change', function (this: HTMLSelectElement) {
+ optionValues[optionName] = JSON.parse(this.value);
updateURLsWithCurrentOptions();
});
const currentValue = optionValues[optionName];
for (const { value, description } of info.selectValueDescriptions!) {
$('<option>')
.text(description)
- .val(value)
+ .val(JSON.stringify(value))
.prop('selected', value === currentValue)
.appendTo(select);
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/wpt.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/wpt.ts
index d4a4008154..79ed1b5924 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/runtime/wpt.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/wpt.ts
@@ -8,8 +8,8 @@ import { parseQuery } from '../internal/query/parseQuery.js';
import { parseExpectationsForTestQuery, relativeQueryString } from '../internal/query/query.js';
import { assert } from '../util/util.js';
-import { optionEnabled } from './helper/options.js';
-import { TestWorker } from './helper/test_worker.js';
+import { optionEnabled, optionWorkerMode } from './helper/options.js';
+import { TestDedicatedWorker, TestServiceWorker, TestSharedWorker } from './helper/test_worker.js';
// testharness.js API (https://web-platform-tests.org/writing-tests/testharness-api.html)
declare interface WptTestObject {
@@ -31,8 +31,10 @@ setup({
});
void (async () => {
- const workerEnabled = optionEnabled('worker');
- const worker = workerEnabled ? new TestWorker() : undefined;
+ const workerString = optionWorkerMode('worker');
+ const dedicatedWorker = workerString === 'dedicated' ? new TestDedicatedWorker() : undefined;
+ const sharedWorker = workerString === 'shared' ? new TestSharedWorker() : undefined;
+ const serviceWorker = workerString === 'service' ? new TestServiceWorker() : undefined;
globalTestConfig.unrollConstEvalLoops = optionEnabled('unroll_const_eval_loops');
@@ -63,8 +65,12 @@ void (async () => {
const wpt_fn = async () => {
const [rec, res] = log.record(name);
- if (worker) {
- await worker.run(rec, name, expectations);
+ if (dedicatedWorker) {
+ await dedicatedWorker.run(rec, name, expectations);
+ } else if (sharedWorker) {
+ await sharedWorker.run(rec, name, expectations);
+ } else if (serviceWorker) {
+ await serviceWorker.run(rec, name, expectations);
} else {
await testcase.run(rec, expectations);
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/tools/crawl.ts b/dom/webgpu/tests/cts/checkout/src/common/tools/crawl.ts
index 50340dd68b..21a335b11c 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/tools/crawl.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/tools/crawl.ts
@@ -45,13 +45,16 @@ async function crawlFilesRecursively(dir: string): Promise<string[]> {
);
}
-export async function crawl(suiteDir: string, validate: boolean): Promise<TestSuiteListingEntry[]> {
+export async function crawl(
+ suiteDir: string,
+ opts: { validate: boolean; printMetadataWarnings: boolean } | null = null
+): Promise<TestSuiteListingEntry[]> {
if (!fs.existsSync(suiteDir)) {
throw new Error(`Could not find suite: ${suiteDir}`);
}
let validateTimingsEntries;
- if (validate) {
+ if (opts?.validate) {
const metadata = loadMetadataForSuite(suiteDir);
if (metadata) {
validateTimingsEntries = {
@@ -75,7 +78,7 @@ export async function crawl(suiteDir: string, validate: boolean): Promise<TestSu
const suite = path.basename(suiteDir);
- if (validate) {
+ if (opts?.validate) {
const filename = `../../${suite}/${filepathWithoutExtension}.spec.js`;
assert(!process.env.STANDALONE_DEV_SERVER);
@@ -109,8 +112,6 @@ export async function crawl(suiteDir: string, validate: boolean): Promise<TestSu
}
if (validateTimingsEntries) {
- let failed = false;
-
const zeroEntries = [];
const staleEntries = [];
for (const [metadataKey, metadataValue] of Object.entries(validateTimingsEntries.metadata)) {
@@ -125,36 +126,39 @@ export async function crawl(suiteDir: string, validate: boolean): Promise<TestSu
staleEntries.push(metadataKey);
}
}
- if (zeroEntries.length) {
- console.warn('WARNING: subcaseMS≤0 found in listing_meta.json (allowed, but try to avoid):');
+ if (zeroEntries.length && opts?.printMetadataWarnings) {
+ console.warn(
+ 'WARNING: subcaseMS ≤ 0 found in listing_meta.json (see docs/adding_timing_metadata.md):'
+ );
for (const metadataKey of zeroEntries) {
console.warn(` ${metadataKey}`);
}
}
- if (staleEntries.length) {
- console.error('ERROR: Non-existent tests found in listing_meta.json:');
- for (const metadataKey of staleEntries) {
- console.error(` ${metadataKey}`);
- }
- failed = true;
- }
- const missingEntries = [];
- for (const metadataKey of validateTimingsEntries.testsFoundInFiles) {
- if (!(metadataKey in validateTimingsEntries.metadata)) {
- missingEntries.push(metadataKey);
+ if (opts?.printMetadataWarnings) {
+ const missingEntries = [];
+ for (const metadataKey of validateTimingsEntries.testsFoundInFiles) {
+ if (!(metadataKey in validateTimingsEntries.metadata)) {
+ missingEntries.push(metadataKey);
+ }
+ }
+ if (missingEntries.length) {
+ console.error(
+ 'WARNING: Tests missing from listing_meta.json (see docs/adding_timing_metadata.md):'
+ );
+ for (const metadataKey of missingEntries) {
+ console.error(` ${metadataKey}`);
+ }
}
}
- if (missingEntries.length) {
- console.error(
- 'ERROR: Tests missing from listing_meta.json. Please add the new tests (See docs/adding_timing_metadata.md):'
- );
- for (const metadataKey of missingEntries) {
+
+ if (staleEntries.length) {
+ console.error('ERROR: Non-existent tests found in listing_meta.json. Please update:');
+ for (const metadataKey of staleEntries) {
console.error(` ${metadataKey}`);
- failed = true;
}
+ unreachable();
}
- assert(!failed);
}
return entries;
@@ -163,5 +167,5 @@ export async function crawl(suiteDir: string, validate: boolean): Promise<TestSu
export function makeListing(filename: string): Promise<TestSuiteListing> {
// Don't validate. This path is only used for the dev server and running tests with Node.
// Validation is done for listing generation and presubmit.
- return crawl(path.dirname(filename), false);
+ return crawl(path.dirname(filename));
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/tools/dev_server.ts b/dom/webgpu/tests/cts/checkout/src/common/tools/dev_server.ts
index 57cb6a7ea4..8e0e3bdbe6 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/tools/dev_server.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/tools/dev_server.ts
@@ -144,6 +144,19 @@ app.get('/out/:suite([a-zA-Z0-9_-]+)/listing.js', async (req, res, next) => {
}
});
+// Serve .worker.js files by generating the necessary wrapper.
+app.get('/out/:suite([a-zA-Z0-9_-]+)/webworker/:filepath(*).worker.js', (req, res, next) => {
+ const { suite, filepath } = req.params;
+ const result = `\
+import { g } from '/out/${suite}/${filepath}.spec.js';
+import { wrapTestGroupForWorker } from '/out/common/runtime/helper/wrap_for_worker.js';
+
+wrapTestGroupForWorker(g);
+`;
+ res.setHeader('Content-Type', 'application/javascript');
+ res.send(result);
+});
+
// Serve all other .js files by fetching the source .ts file and compiling it.
app.get('/out/**/*.js', async (req, res, next) => {
const jsUrl = path.relative('/out', req.url);
diff --git a/dom/webgpu/tests/cts/checkout/src/common/tools/gen_cache.ts b/dom/webgpu/tests/cts/checkout/src/common/tools/gen_cache.ts
index ce0854aa20..d8309ebcb1 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/tools/gen_cache.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/tools/gen_cache.ts
@@ -3,32 +3,41 @@ import * as path from 'path';
import * as process from 'process';
import { Cacheable, dataCache, setIsBuildingDataCache } from '../framework/data_cache.js';
+import { crc32, toHexString } from '../util/crc32.js';
+import { parseImports } from '../util/parse_imports.js';
function usage(rc: number): void {
- console.error(`Usage: tools/gen_cache [options] [OUT_DIR] [SUITE_DIRS...]
+ console.error(`Usage: tools/gen_cache [options] [SUITE_DIRS...]
For each suite in SUITE_DIRS, pre-compute data that is expensive to generate
-at runtime and store it under OUT_DIR. If the data file is found then the
-DataCache will load this instead of building the expensive data at CTS runtime.
+at runtime and store it under 'src/resources/cache'. If the data file is found
+then the DataCache will load this instead of building the expensive data at CTS
+runtime.
+Note: Due to differences in gzip compression, different versions of node can
+produce radically different binary cache files. gen_cache uses the hashes of the
+source files to determine whether a cache file is 'up to date'. This is faster
+and does not depend on the compressed output.
Options:
--help Print this message and exit.
--list Print the list of output files without writing them.
- --nth i/n Only process every file where (file_index % n == i)
- --validate Check that cache should build (Tests for collisions).
+ --force Rebuild cache even if they're up to date
+ --validate Check the cache is up to date
--verbose Print each action taken.
`);
process.exit(rc);
}
+// Where the cache is generated
+const outDir = 'src/resources/cache';
+
+let forceRebuild = false;
let mode: 'emit' | 'list' | 'validate' = 'emit';
-let nth = { i: 0, n: 1 };
let verbose = false;
const nonFlagsArgs: string[] = [];
-for (let i = 0; i < process.argv.length; i++) {
- const arg = process.argv[i];
+for (const arg of process.argv) {
if (arg.startsWith('-')) {
switch (arg) {
case '--list': {
@@ -39,6 +48,10 @@ for (let i = 0; i < process.argv.length; i++) {
usage(0);
break;
}
+ case '--force': {
+ forceRebuild = true;
+ break;
+ }
case '--verbose': {
verbose = true;
break;
@@ -47,28 +60,6 @@ for (let i = 0; i < process.argv.length; i++) {
mode = 'validate';
break;
}
- case '--nth': {
- const err = () => {
- console.error(
- `--nth requires a value of the form 'i/n', where i and n are positive integers and i < n`
- );
- process.exit(1);
- };
- i++;
- if (i >= process.argv.length) {
- err();
- }
- const value = process.argv[i];
- const parts = value.split('/');
- if (parts.length !== 2) {
- err();
- }
- nth = { i: parseInt(parts[0]), n: parseInt(parts[1]) };
- if (nth.i < 0 || nth.n < 1 || nth.i > nth.n) {
- err();
- }
- break;
- }
default: {
console.log('unrecognized flag: ', arg);
usage(1);
@@ -79,12 +70,10 @@ for (let i = 0; i < process.argv.length; i++) {
}
}
-if (nonFlagsArgs.length < 4) {
+if (nonFlagsArgs.length < 3) {
usage(0);
}
-const outRootDir = nonFlagsArgs[2];
-
dataCache.setStore({
load: (path: string) => {
return new Promise<Uint8Array>((resolve, reject) => {
@@ -100,57 +89,133 @@ dataCache.setStore({
});
setIsBuildingDataCache();
+const cacheFileSuffix = __filename.endsWith('.ts') ? '.cache.ts' : '.cache.js';
+
+/**
+ * @returns a list of all the files under 'dir' that has the given extension
+ * @param dir the directory to search
+ * @param ext the extension of the files to find
+ */
+function glob(dir: string, ext: string) {
+ const files: string[] = [];
+ for (const file of fs.readdirSync(dir)) {
+ const path = `${dir}/${file}`;
+ if (fs.statSync(path).isDirectory()) {
+ for (const child of glob(path, ext)) {
+ files.push(`${file}/${child}`);
+ }
+ }
+
+ if (path.endsWith(ext) && fs.statSync(path).isFile()) {
+ files.push(file);
+ }
+ }
+ return files;
+}
+
+/**
+ * Exception type thrown by SourceHasher.hashFile() when a file annotated with
+ * MUST_NOT_BE_IMPORTED_BY_DATA_CACHE is transitively imported by a .cache.ts file.
+ */
+class InvalidImportException {
+ constructor(path: string) {
+ this.stack = [path];
+ }
+ toString(): string {
+ return `invalid transitive import for cache:\n ${this.stack.join('\n ')}`;
+ }
+ readonly stack: string[];
+}
+/**
+ * SourceHasher is a utility for producing a hash of a source .ts file and its imported source files.
+ */
+class SourceHasher {
+ /**
+ * @param path the source file path
+ * @returns a hash of the source file and all of its imported dependencies.
+ */
+ public hashOf(path: string) {
+ this.u32Array[0] = this.hashFile(path);
+ return this.u32Array[0].toString(16);
+ }
+
+ hashFile(path: string): number {
+ if (!fs.existsSync(path) && path.endsWith('.js')) {
+ path = path.substring(0, path.length - 2) + 'ts';
+ }
+
+ const cached = this.hashes.get(path);
+ if (cached !== undefined) {
+ return cached;
+ }
+
+ this.hashes.set(path, 0); // Store a zero hash to handle cyclic imports
+
+ const content = fs.readFileSync(path, { encoding: 'utf-8' });
+ const normalized = content.replace('\r\n', '\n');
+ let hash = crc32(normalized);
+ for (const importPath of parseImports(path, normalized)) {
+ try {
+ const importHash = this.hashFile(importPath);
+ hash = this.hashCombine(hash, importHash);
+ } catch (ex) {
+ if (ex instanceof InvalidImportException) {
+ ex.stack.push(path);
+ throw ex;
+ }
+ }
+ }
+
+ if (content.includes('MUST_NOT_BE_IMPORTED_BY_DATA_CACHE')) {
+ throw new InvalidImportException(path);
+ }
+
+ this.hashes.set(path, hash);
+ return hash;
+ }
+
+ /** Simple non-cryptographic hash combiner */
+ hashCombine(a: number, b: number): number {
+ return crc32(`${toHexString(a)} ${toHexString(b)}`);
+ }
+
+ private hashes = new Map<string, number>();
+ private u32Array = new Uint32Array(1);
+}
+
void (async () => {
- for (const suiteDir of nonFlagsArgs.slice(3)) {
+ const suiteDirs = nonFlagsArgs.slice(2); // skip <exe> <js>
+ for (const suiteDir of suiteDirs) {
await build(suiteDir);
}
})();
-const specFileSuffix = __filename.endsWith('.ts') ? '.spec.ts' : '.spec.js';
-
-async function crawlFilesRecursively(dir: string): Promise<string[]> {
- const subpathInfo = await Promise.all(
- (await fs.promises.readdir(dir)).map(async d => {
- const p = path.join(dir, d);
- const stats = await fs.promises.stat(p);
- return {
- path: p,
- isDirectory: stats.isDirectory(),
- isFile: stats.isFile(),
- };
- })
- );
-
- const files = subpathInfo
- .filter(i => i.isFile && i.path.endsWith(specFileSuffix))
- .map(i => i.path);
-
- return files.concat(
- await subpathInfo
- .filter(i => i.isDirectory)
- .map(i => crawlFilesRecursively(i.path))
- .reduce(async (a, b) => (await a).concat(await b), Promise.resolve([]))
- );
-}
-
async function build(suiteDir: string) {
if (!fs.existsSync(suiteDir)) {
console.error(`Could not find ${suiteDir}`);
process.exit(1);
}
- // Crawl files and convert paths to be POSIX-style, relative to suiteDir.
- let filesToEnumerate = (await crawlFilesRecursively(suiteDir)).sort();
+ // Load hashes.json
+ const fileHashJsonPath = `${outDir}/hashes.json`;
+ let fileHashes: Record<string, string> = {};
+ if (fs.existsSync(fileHashJsonPath)) {
+ const json = fs.readFileSync(fileHashJsonPath, { encoding: 'utf8' });
+ fileHashes = JSON.parse(json);
+ }
- // Filter out non-spec files
- filesToEnumerate = filesToEnumerate.filter(f => f.endsWith(specFileSuffix));
+ // Crawl files and convert paths to be POSIX-style, relative to suiteDir.
+ const filesToEnumerate = glob(suiteDir, cacheFileSuffix)
+ .map(p => `${suiteDir}/${p}`)
+ .sort();
+ const fileHasher = new SourceHasher();
const cacheablePathToTS = new Map<string, string>();
+ const errors: Array<string> = [];
- let fileIndex = 0;
for (const file of filesToEnumerate) {
- const pathWithoutExtension = file.substring(0, file.length - specFileSuffix.length);
- const mod = await import(`../../../${pathWithoutExtension}.spec.js`);
+ const pathWithoutExtension = file.substring(0, file.length - 3);
+ const mod = await import(`../../../${pathWithoutExtension}.js`);
if (mod.d?.serialize !== undefined) {
const cacheable = mod.d as Cacheable<unknown>;
@@ -158,41 +223,78 @@ async function build(suiteDir: string) {
// Check for collisions
const existing = cacheablePathToTS.get(cacheable.path);
if (existing !== undefined) {
- console.error(
- `error: Cacheable '${cacheable.path}' is emitted by both:
+ errors.push(
+ `'${cacheable.path}' is emitted by both:
'${existing}'
and
'${file}'`
);
- process.exit(1);
}
cacheablePathToTS.set(cacheable.path, file);
}
- const outPath = `${outRootDir}/data/${cacheable.path}`;
+ const outPath = `${outDir}/${cacheable.path}`;
+ const fileHash = fileHasher.hashOf(file);
- if (fileIndex++ % nth.n === nth.i) {
- switch (mode) {
- case 'emit': {
+ switch (mode) {
+ case 'emit': {
+ if (!forceRebuild && fileHashes[cacheable.path] === fileHash) {
if (verbose) {
- console.log(`building '${outPath}'`);
+ console.log(`'${outPath}' is up to date`);
}
- const data = await cacheable.build();
- const serialized = cacheable.serialize(data);
- fs.mkdirSync(path.dirname(outPath), { recursive: true });
- fs.writeFileSync(outPath, serialized, 'binary');
- break;
+ continue;
}
- case 'list': {
- console.log(outPath);
- break;
- }
- case 'validate': {
- // Only check currently performed is the collision detection above
- break;
+ console.log(`building '${outPath}'`);
+ const data = await cacheable.build();
+ const serialized = cacheable.serialize(data);
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
+ fs.writeFileSync(outPath, serialized, 'binary');
+ fileHashes[cacheable.path] = fileHash;
+ break;
+ }
+ case 'list': {
+ console.log(outPath);
+ break;
+ }
+ case 'validate': {
+ if (fileHashes[cacheable.path] !== fileHash) {
+ errors.push(
+ `'${outPath}' needs rebuilding. Generate with 'npx grunt run:generate-cache'`
+ );
+ } else if (verbose) {
+ console.log(`'${outPath}' is up to date`);
}
}
}
}
}
+
+ // Check that there aren't stale files in the cache directory
+ for (const file of glob(outDir, '.bin')) {
+ if (cacheablePathToTS.get(file) === undefined) {
+ switch (mode) {
+ case 'emit':
+ fs.rmSync(file);
+ break;
+ case 'validate':
+ errors.push(
+ `cache file '${outDir}/${file}' is no longer generated. Remove with 'npx grunt run:generate-cache'`
+ );
+ break;
+ }
+ }
+ }
+
+ // Update hashes.json
+ if (mode === 'emit') {
+ const json = JSON.stringify(fileHashes, undefined, ' ');
+ fs.writeFileSync(fileHashJsonPath, json, { encoding: 'utf8' });
+ }
+
+ if (errors.length > 0) {
+ for (const error of errors) {
+ console.error(error);
+ }
+ process.exit(1);
+ }
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/tools/gen_listings.ts b/dom/webgpu/tests/cts/checkout/src/common/tools/gen_listings.ts
index fc5e1f3cde..7cc8cb78f3 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/tools/gen_listings.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/tools/gen_listings.ts
@@ -9,7 +9,7 @@ function usage(rc: number): void {
For each suite in SUITE_DIRS, generate listings and write each listing.js
into OUT_DIR/{suite}/listing.js. Example:
- tools/gen_listings out/ src/unittests/ src/webgpu/
+ tools/gen_listings gen/ src/unittests/ src/webgpu/
Options:
--help Print this message and exit.
@@ -40,7 +40,7 @@ const outDir = argv[2];
for (const suiteDir of argv.slice(3)) {
// Run concurrently for each suite (might be a tiny bit more efficient)
- void crawl(suiteDir, false).then(listing => {
+ void crawl(suiteDir).then(listing => {
const suite = path.basename(suiteDir);
const outFile = path.normalize(path.join(outDir, `${suite}/listing.js`));
fs.mkdirSync(path.join(outDir, suite), { recursive: true });
@@ -52,12 +52,5 @@ for (const suiteDir of argv.slice(3)) {
export const listing = ${JSON.stringify(listing, undefined, 2)};
`
);
-
- // If there was a sourcemap for the file we just replaced, delete it.
- try {
- fs.unlinkSync(outFile + '.map');
- } catch (ex) {
- // ignore if file didn't exist
- }
});
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/tools/gen_listings_and_webworkers.ts b/dom/webgpu/tests/cts/checkout/src/common/tools/gen_listings_and_webworkers.ts
new file mode 100644
index 0000000000..04ce669de3
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/common/tools/gen_listings_and_webworkers.ts
@@ -0,0 +1,89 @@
+import * as fs from 'fs';
+import * as path from 'path';
+import * as process from 'process';
+
+import { crawl } from './crawl.js';
+
+function usage(rc: number): void {
+ console.error(`Usage: tools/gen_listings_and_webworkers [options] [OUT_DIR] [SUITE_DIRS...]
+
+For each suite in SUITE_DIRS, generate listings into OUT_DIR/{suite}/listing.js,
+and generate Web Worker proxies in OUT_DIR/{suite}/webworker/**/*.worker.js for
+every .spec.js file. (Note {suite}/webworker/ is reserved for this purpose.)
+
+Example:
+ tools/gen_listings_and_webworkers gen/ src/unittests/ src/webgpu/
+
+Options:
+ --help Print this message and exit.
+`);
+ process.exit(rc);
+}
+
+const argv = process.argv;
+if (argv.indexOf('--help') !== -1) {
+ usage(0);
+}
+
+{
+ // Ignore old argument that is now the default
+ const i = argv.indexOf('--no-validate');
+ if (i !== -1) {
+ argv.splice(i, 1);
+ }
+}
+
+if (argv.length < 4) {
+ usage(0);
+}
+
+const myself = 'src/common/tools/gen_listings_and_webworkers.ts';
+
+const outDir = argv[2];
+
+for (const suiteDir of argv.slice(3)) {
+ // Run concurrently for each suite (might be a tiny bit more efficient)
+ void crawl(suiteDir).then(listing => {
+ const suite = path.basename(suiteDir);
+
+ // Write listing.js
+ const outFile = path.normalize(path.join(outDir, `${suite}/listing.js`));
+ fs.mkdirSync(path.join(outDir, suite), { recursive: true });
+ fs.writeFileSync(
+ outFile,
+ `\
+// AUTO-GENERATED - DO NOT EDIT. See ${myself}.
+
+export const listing = ${JSON.stringify(listing, undefined, 2)};
+`
+ );
+
+ // Write suite/webworker/**/*.worker.js
+ for (const entry of listing) {
+ if ('readme' in entry) continue;
+
+ const outFileDir = path.join(
+ outDir,
+ suite,
+ 'webworker',
+ ...entry.file.slice(0, entry.file.length - 1)
+ );
+ const outFile = path.join(outDir, suite, 'webworker', ...entry.file) + '.worker.js';
+
+ const relPathToSuiteRoot = Array<string>(entry.file.length).fill('..').join('/');
+
+ fs.mkdirSync(outFileDir, { recursive: true });
+ fs.writeFileSync(
+ outFile,
+ `\
+// AUTO-GENERATED - DO NOT EDIT. See ${myself}.
+
+import { g } from '${relPathToSuiteRoot}/${entry.file.join('/')}.spec.js';
+import { wrapTestGroupForWorker } from '${relPathToSuiteRoot}/../common/runtime/helper/wrap_for_worker.js';
+
+wrapTestGroupForWorker(g);
+`
+ );
+ }
+ });
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/tools/gen_wpt_cts_html.ts b/dom/webgpu/tests/cts/checkout/src/common/tools/gen_wpt_cts_html.ts
index e8161304e9..46c2ae4354 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/tools/gen_wpt_cts_html.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/tools/gen_wpt_cts_html.ts
@@ -23,6 +23,7 @@ gen_wpt_cts_html.ts. Example:
{
"suite": "webgpu",
"out": "path/to/output/cts.https.html",
+ "outJSON": "path/to/output/webgpu_variant_list.json",
"template": "path/to/template/cts.https.html",
"maxChunkTimeMS": 2000
}
@@ -35,15 +36,15 @@ where arguments.txt is a file containing a list of arguments prefixes to both ge
in the expectations. The entire variant list generation runs *once per prefix*, so this
multiplies the size of the variant list.
- ?worker=0&q=
- ?worker=1&q=
+ ?debug=0&q=
+ ?debug=1&q=
and myexpectations.txt is a file containing a list of WPT paths to suppress, e.g.:
- path/to/cts.https.html?worker=0&q=webgpu:a/foo:bar={"x":1}
- path/to/cts.https.html?worker=1&q=webgpu:a/foo:bar={"x":1}
+ path/to/cts.https.html?debug=0&q=webgpu:a/foo:bar={"x":1}
+ path/to/cts.https.html?debug=1&q=webgpu:a/foo:bar={"x":1}
- path/to/cts.https.html?worker=1&q=webgpu:a/foo:bar={"x":3}
+ path/to/cts.https.html?debug=1&q=webgpu:a/foo:bar={"x":3}
`);
process.exit(rc);
}
@@ -51,9 +52,11 @@ and myexpectations.txt is a file containing a list of WPT paths to suppress, e.g
interface ConfigJSON {
/** Test suite to generate from. */
suite: string;
- /** Output filename, relative to JSON file. */
+ /** Output path for HTML file, relative to config file. */
out: string;
- /** Input template filename, relative to JSON file. */
+ /** Output path for JSON file containing the "variant" list, relative to config file. */
+ outVariantList?: string;
+ /** Input template filename, relative to config file. */
template: string;
/**
* Maximum time for a single WPT "variant" chunk, in milliseconds. Defaults to infinity.
@@ -71,18 +74,31 @@ interface ConfigJSON {
/** The prefix to trim from every line of the expectations_file. */
prefix: string;
};
+ /** Expend all subtrees for provided queries */
+ fullyExpandSubtrees?: {
+ file: string;
+ prefix: string;
+ };
+ /*No long path assert */
+ noLongPathAssert?: boolean;
}
interface Config {
suite: string;
out: string;
+ outVariantList?: string;
template: string;
maxChunkTimeMS: number;
argumentsPrefixes: string[];
+ noLongPathAssert: boolean;
expectations?: {
file: string;
prefix: string;
};
+ fullyExpandSubtrees?: {
+ file: string;
+ prefix: string;
+ };
}
let config: Config;
@@ -101,13 +117,23 @@ let config: Config;
template: path.resolve(jsonFileDir, configJSON.template),
maxChunkTimeMS: configJSON.maxChunkTimeMS ?? Infinity,
argumentsPrefixes: configJSON.argumentsPrefixes ?? ['?q='],
+ noLongPathAssert: configJSON.noLongPathAssert ?? false,
};
+ if (configJSON.outVariantList) {
+ config.outVariantList = path.resolve(jsonFileDir, configJSON.outVariantList);
+ }
if (configJSON.expectations) {
config.expectations = {
file: path.resolve(jsonFileDir, configJSON.expectations.file),
prefix: configJSON.expectations.prefix,
};
}
+ if (configJSON.fullyExpandSubtrees) {
+ config.fullyExpandSubtrees = {
+ file: path.resolve(jsonFileDir, configJSON.fullyExpandSubtrees.file),
+ prefix: configJSON.fullyExpandSubtrees.prefix,
+ };
+ }
break;
}
case 4:
@@ -130,6 +156,7 @@ let config: Config;
suite,
maxChunkTimeMS: Infinity,
argumentsPrefixes: ['?q='],
+ noLongPathAssert: false,
};
if (process.argv.length >= 7) {
config.argumentsPrefixes = (await fs.readFile(argsPrefixesFile, 'utf8'))
@@ -153,29 +180,16 @@ let config: Config;
config.argumentsPrefixes.sort((a, b) => b.length - a.length);
// Load expectations (if any)
- let expectationLines = new Set<string>();
- if (config.expectations) {
- expectationLines = new Set(
- (await fs.readFile(config.expectations.file, 'utf8')).split(/\r?\n/).filter(l => l.length)
- );
- }
+ const expectations: Map<string, string[]> = await loadQueryFile(
+ config.argumentsPrefixes,
+ config.expectations
+ );
- const expectations: Map<string, string[]> = new Map();
- 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 config.argumentsPrefixes) {
- const prefix = config.expectations!.prefix + argsPrefix;
- if (exp.startsWith(prefix)) {
- expectations.get(argsPrefix)!.push(exp.substring(prefix.length));
- continue expLoop;
- }
- }
- console.log('note: ignored expectation: ' + exp);
- }
+ // Load fullyExpandSubtrees queries (if any)
+ const fullyExpand: Map<string, string[]> = await loadQueryFile(
+ config.argumentsPrefixes,
+ config.fullyExpandSubtrees
+ );
const loader = new DefaultTestFileLoader();
const lines = [];
@@ -183,6 +197,7 @@ let config: Config;
const rootQuery = new TestQueryMultiFile(config.suite, []);
const tree = await loader.loadTree(rootQuery, {
subqueriesToExpand: expectations.get(prefix),
+ fullyExpandSubtrees: fullyExpand.get(prefix),
maxChunkTime: config.maxChunkTimeMS,
});
@@ -199,22 +214,24 @@ let config: Config;
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(
- queryString.length < 185,
- `Generated test variant would produce too-long -actual.txt filename. Possible solutions:
+ if (!config.noLongPathAssert) {
+ 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(
+ 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=..."
+ urlQueryString: prefix + query.toString(), // "?debug=0&q=..."
comment: useChunking ? `estimated: ${subtreeCounts?.totalTimeMS.toFixed(3)} ms` : undefined,
});
@@ -232,6 +249,39 @@ ${queryString}`
process.exit(1);
});
+async function loadQueryFile(
+ argumentsPrefixes: string[],
+ queryFile?: {
+ file: string;
+ prefix: string;
+ }
+): Promise<Map<string, string[]>> {
+ let lines = new Set<string>();
+ if (queryFile) {
+ lines = new Set(
+ (await fs.readFile(queryFile.file, 'utf8')).split(/\r?\n/).filter(l => l.length)
+ );
+ }
+
+ const result: Map<string, string[]> = new Map();
+ for (const prefix of argumentsPrefixes) {
+ result.set(prefix, []);
+ }
+
+ expLoop: for (const exp of lines) {
+ // Take each expectation for the longest prefix it matches.
+ for (const argsPrefix of argumentsPrefixes) {
+ const prefix = queryFile!.prefix + argsPrefix;
+ if (exp.startsWith(prefix)) {
+ result.get(argsPrefix)!.push(exp.substring(prefix.length));
+ continue expLoop;
+ }
+ }
+ console.log('note: ignored expectation: ' + exp);
+ }
+ return result;
+}
+
async function generateFile(
lines: Array<{ urlQueryString?: string; comment?: string } | undefined>
): Promise<void> {
@@ -240,13 +290,20 @@ async function generateFile(
result += await fs.readFile(config.template, 'utf8');
+ const variantList = [];
for (const line of lines) {
if (line !== undefined) {
- if (line.urlQueryString) result += `<meta name=variant content='${line.urlQueryString}'>`;
+ if (line.urlQueryString) {
+ result += `<meta name=variant content='${line.urlQueryString}'>`;
+ variantList.push(line.urlQueryString);
+ }
if (line.comment) result += `<!-- ${line.comment} -->`;
}
result += '\n';
}
await fs.writeFile(config.out, result);
+ if (config.outVariantList) {
+ await fs.writeFile(config.outVariantList, JSON.stringify(variantList, undefined, 2));
+ }
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/tools/merge_listing_times.ts b/dom/webgpu/tests/cts/checkout/src/common/tools/merge_listing_times.ts
index fb33ae20fb..a8bef354cc 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/tools/merge_listing_times.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/tools/merge_listing_times.ts
@@ -36,21 +36,13 @@ In more detail:
- 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\`
+See 'docs/adding_timing_metadata.md' for how to generate TIMING_LOG_FILES files.
`);
process.exit(rc);
}
const kHeader = `{
- "_comment": "SEMI AUTO-GENERATED: Please read docs/adding_timing_metadata.md.",
+ "_comment": "SEMI AUTO-GENERATED. This list is NOT exhaustive. Please read docs/adding_timing_metadata.md.",
`;
const kFooter = `\
"_end": ""
diff --git a/dom/webgpu/tests/cts/checkout/src/common/tools/validate.ts b/dom/webgpu/tests/cts/checkout/src/common/tools/validate.ts
index 164ee3259a..47aa9782a8 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/tools/validate.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/tools/validate.ts
@@ -2,7 +2,7 @@ import * as process from 'process';
import { crawl } from './crawl.js';
-function usage(rc: number): void {
+function usage(rc: number): never {
console.error(`Usage: tools/validate [options] [SUITE_DIRS...]
For each suite in SUITE_DIRS, validate some properties about the file:
@@ -14,23 +14,40 @@ For each suite in SUITE_DIRS, validate some properties about the file:
- That each case query is not too long
Example:
- tools/validate src/unittests/ src/webgpu/
+ tools/validate src/unittests src/webgpu
Options:
- --help Print this message and exit.
+ --help Print this message and exit.
+ --print-metadata-warnings Print non-fatal warnings about listing_meta.json files.
`);
process.exit(rc);
}
const args = process.argv.slice(2);
+if (args.length < 1) {
+ usage(0);
+}
if (args.indexOf('--help') !== -1) {
usage(0);
}
-if (args.length < 1) {
+let printMetadataWarnings = false;
+const suiteDirs = [];
+for (const arg of args) {
+ if (arg === '--print-metadata-warnings') {
+ printMetadataWarnings = true;
+ } else {
+ suiteDirs.push(arg);
+ }
+}
+
+if (suiteDirs.length === 0) {
usage(0);
}
-for (const suiteDir of args) {
- void crawl(suiteDir, true);
+for (const suiteDir of suiteDirs) {
+ void crawl(suiteDir, {
+ validate: true,
+ printMetadataWarnings,
+ });
}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/util/crc32.ts b/dom/webgpu/tests/cts/checkout/src/common/util/crc32.ts
new file mode 100644
index 0000000000..5f74b4662e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/common/util/crc32.ts
@@ -0,0 +1,57 @@
+/// CRC32 immutable lookup table data.
+const kCRC32LUT = [
+ 0, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832,
+ 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064,
+ 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856,
+ 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8,
+ 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa,
+ 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac,
+ 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e,
+ 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190,
+ 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2,
+ 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4,
+ 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6,
+ 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158,
+ 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
+ 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c,
+ 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e,
+ 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320,
+ 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12,
+ 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344,
+ 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76,
+ 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8,
+ 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda,
+ 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c,
+ 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe,
+ 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0,
+ 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82,
+ 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4,
+ 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6,
+ 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
+ 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a,
+ 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c,
+ 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e,
+ 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
+];
+
+/**
+ * @param str the input string
+ * @returns the CRC32 of the input string
+ * @see https://en.wikipedia.org/wiki/Cyclic_redundancy_check#CRC-32_algorithm
+ */
+export function crc32(str: string): number {
+ const utf8 = new TextEncoder().encode(str);
+ const u32 = new Uint32Array(1);
+
+ u32[0] = 0xffffffff;
+ for (const c of utf8) {
+ u32[0] = (u32[0] >>> 8) ^ kCRC32LUT[(u32[0] & 0xff) ^ c];
+ }
+ u32[0] = u32[0] ^ 0xffffffff;
+ return u32[0];
+}
+
+/** @returns the input number has a 8-character hex string */
+export function toHexString(number: number): string {
+ return ('00000000' + number.toString(16)).slice(-8);
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/util/parse_imports.ts b/dom/webgpu/tests/cts/checkout/src/common/util/parse_imports.ts
new file mode 100644
index 0000000000..4b5604b897
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/common/util/parse_imports.ts
@@ -0,0 +1,36 @@
+/**
+ * Parses all the paths of the typescript `import` statements from content
+ * @param path the current path of the file
+ * @param content the file content
+ * @returns the list of import paths
+ */
+export function parseImports(path: string, content: string): string[] {
+ const out: string[] = [];
+ const importRE = /^import\s[^'"]*(['"])([./\w]*)(\1);/gm;
+ let importMatch: RegExpMatchArray | null;
+ while ((importMatch = importRE.exec(content))) {
+ const importPath = importMatch[2].replace(`'`, '').replace(`"`, '');
+ out.push(joinPath(path, importPath));
+ }
+ return out;
+}
+
+function joinPath(a: string, b: string): string {
+ const aParts = a.split('/');
+ const bParts = b.split('/');
+ aParts.pop(); // remove file
+ let bStart = 0;
+ while (aParts.length > 0) {
+ switch (bParts[bStart]) {
+ case '.':
+ bStart++;
+ continue;
+ case '..':
+ aParts.pop();
+ bStart++;
+ continue;
+ }
+ break;
+ }
+ return [...aParts, ...bParts.slice(bStart)].join('/');
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/common/util/util.ts b/dom/webgpu/tests/cts/checkout/src/common/util/util.ts
index 9433aaddb0..4092b997a3 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/util/util.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/util/util.ts
@@ -1,7 +1,6 @@
import { Float16Array } from '../../external/petamoriken/float16/float16.js';
import { SkipTestCase } from '../framework/fixture.js';
import { globalTestConfig } from '../framework/test_config.js';
-import { Logger } from '../internal/logging/logger.js';
import { keysOf } from './data_tables.js';
import { timeout } from './timeout.js';
@@ -24,7 +23,7 @@ export class ErrorWithExtra extends Error {
super(message);
const oldExtras = baseOrMessage instanceof ErrorWithExtra ? baseOrMessage.extra : {};
- this.extra = Logger.globalDebugMode
+ this.extra = globalTestConfig.enableDebugLogs
? { ...oldExtras, ...newExtra() }
: { omitted: 'pass ?debug=1' };
}
@@ -304,6 +303,8 @@ const TypedArrayBufferViewInstances = [
new Float16Array(),
new Float32Array(),
new Float64Array(),
+ new BigInt64Array(),
+ new BigUint64Array(),
] as const;
export type TypedArrayBufferView = (typeof TypedArrayBufferViewInstances)[number];
diff --git a/dom/webgpu/tests/cts/checkout/src/external/README.md b/dom/webgpu/tests/cts/checkout/src/external/README.md
index 84fbf9c732..29c5f41f3a 100644
--- a/dom/webgpu/tests/cts/checkout/src/external/README.md
+++ b/dom/webgpu/tests/cts/checkout/src/external/README.md
@@ -28,4 +28,4 @@ should be listed below.
| **Name** | **Origin** | **License** | **Version** | **Purpose** |
|----------------------|--------------------------------------------------|-------------|-------------|------------------------------------------------|
-| petamoriken/float16 | [github](https://github.com/petamoriken/float16) | MIT | 3.6.6 | Fluent support for f16 numbers via TypedArrays |
+| petamoriken/float16 | [github](https://github.com/petamoriken/float16) | MIT | 3.8.6 | Fluent support for f16 numbers via TypedArrays |
diff --git a/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/LICENSE.txt b/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/LICENSE.txt
index e8eacf4e7f..e3d7962fe8 100644
--- a/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/LICENSE.txt
+++ b/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/LICENSE.txt
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2017-2021 Kenta Moriuchi
+Copyright (c) 2017-2024 Kenta Moriuchi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.d.ts b/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.d.ts
index c9d66ab7ca..7e79bc177c 100644
--- a/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.d.ts
+++ b/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.d.ts
@@ -331,6 +331,26 @@ export interface Float16Array {
subarray(begin?: number, end?: number): Float16Array;
/**
+ * Copies the array and returns the copy with the elements in reverse order.
+ */
+ toReversed(): Float16Array;
+
+ /**
+ * Copies and sorts the array.
+ * @param compareFn Function used to determine the order of the elements. It is expected to return
+ * a negative value if first argument is less than second argument, zero if they're equal and a positive
+ * value otherwise. If omitted, the elements are sorted in ascending.
+ */
+ toSorted(compareFn?: (a: number, b: number) => number): Float16Array;
+
+ /**
+ * Copies the array and replaces the element at the given index with the provided value.
+ * @param index The zero-based location in the array for which to replace an element.
+ * @param value Element to insert into the array in place of the replaced element.
+ */
+ with(index: number, value: number): Float16Array;
+
+ /**
* Converts a number to a string by using the current locale.
*/
toLocaleString(): string;
@@ -468,4 +488,11 @@ export declare function setFloat16(
* Returns the nearest half-precision float representation of a number.
* @param x A numeric expression.
*/
+export declare function f16round(x: number): number;
+
+/**
+ * Returns the nearest half-precision float representation of a number.
+ * @alias f16round
+ * @param x A numeric expression.
+ */
export declare function hfround(x: number): number;
diff --git a/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.js b/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.js
index 54843a4842..1031e2bcda 100644
--- a/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.js
+++ b/dom/webgpu/tests/cts/checkout/src/external/petamoriken/float16/float16.js
@@ -1,4 +1,4 @@
-/*! @petamoriken/float16 v3.6.6 | MIT License - https://github.com/petamoriken/float16 */
+/*! @petamoriken/float16 v3.8.6 | MIT License - https://github.com/petamoriken/float16 */
const THIS_IS_NOT_AN_OBJECT = "This is not an object";
const THIS_IS_NOT_A_FLOAT16ARRAY_OBJECT = "This is not a Float16Array object";
@@ -19,6 +19,8 @@ const CANNOT_MIX_BIGINT_AND_OTHER_TYPES =
const ITERATOR_PROPERTY_IS_NOT_CALLABLE = "@@iterator property is not callable";
const REDUCE_OF_EMPTY_ARRAY_WITH_NO_INITIAL_VALUE =
"Reduce of empty array with no initial value";
+const THE_COMPARISON_FUNCTION_MUST_BE_EITHER_A_FUNCTION_OR_UNDEFINED =
+ "The comparison function must be either a function or undefined";
const OFFSET_IS_OUT_OF_BOUNDS = "Offset is out of bounds";
function uncurryThis(target) {
@@ -48,7 +50,8 @@ const {
} = Reflect;
const NativeProxy = Proxy;
const {
- MAX_SAFE_INTEGER: MAX_SAFE_INTEGER,
+ EPSILON,
+ MAX_SAFE_INTEGER,
isFinite: NumberIsFinite,
isNaN: NumberIsNaN,
} = Number;
@@ -97,7 +100,10 @@ const ArrayPrototypeToLocaleString = uncurryThis(
);
const NativeArrayPrototypeSymbolIterator = ArrayPrototype[SymbolIterator];
const ArrayPrototypeSymbolIterator = uncurryThis(NativeArrayPrototypeSymbolIterator);
-const MathTrunc = Math.trunc;
+const {
+ abs: MathAbs,
+ trunc: MathTrunc,
+} = Math;
const NativeArrayBuffer = ArrayBuffer;
const ArrayBufferIsView = NativeArrayBuffer.isView;
const ArrayBufferPrototype = NativeArrayBuffer.prototype;
@@ -146,6 +152,7 @@ const TypedArrayPrototypeGetSymbolToStringTag = uncurryThisGetter(
TypedArrayPrototype,
SymbolToStringTag
);
+const NativeUint8Array = Uint8Array;
const NativeUint16Array = Uint16Array;
const Uint16ArrayFrom = (...args) => {
return ReflectApply(TypedArrayFrom, NativeUint16Array, args);
@@ -190,7 +197,10 @@ const SafeIteratorPrototype = ObjectCreate(null, {
},
});
function safeIfNeeded(array) {
- if (array[SymbolIterator] === NativeArrayPrototypeSymbolIterator) {
+ if (
+ array[SymbolIterator] === NativeArrayPrototypeSymbolIterator &&
+ ArrayIteratorPrototype.next === ArrayIteratorPrototypeNext
+ ) {
return array;
}
const safe = ObjectCreate(SafeIteratorPrototype);
@@ -221,8 +231,10 @@ function wrap(generator) {
}
function isObject(value) {
- return (value !== null && typeof value === "object") ||
- typeof value === "function";
+ return (
+ (value !== null && typeof value === "object") ||
+ typeof value === "function"
+ );
}
function isObjectLike(value) {
return value !== null && typeof value === "object";
@@ -232,11 +244,16 @@ function isNativeTypedArray(value) {
}
function isNativeBigIntTypedArray(value) {
const typedArrayName = TypedArrayPrototypeGetSymbolToStringTag(value);
- return typedArrayName === "BigInt64Array" ||
- typedArrayName === "BigUint64Array";
+ return (
+ typedArrayName === "BigInt64Array" ||
+ typedArrayName === "BigUint64Array"
+ );
}
function isArrayBuffer(value) {
try {
+ if (ArrayIsArray(value)) {
+ return false;
+ }
ArrayBufferPrototypeGetByteLength( (value));
return true;
} catch (e) {
@@ -254,25 +271,26 @@ function isSharedArrayBuffer(value) {
return false;
}
}
+function isAnyArrayBuffer(value) {
+ return isArrayBuffer(value) || isSharedArrayBuffer(value);
+}
function isOrdinaryArray(value) {
if (!ArrayIsArray(value)) {
return false;
}
- if (value[SymbolIterator] === NativeArrayPrototypeSymbolIterator) {
- return true;
- }
- const iterator = value[SymbolIterator]();
- return iterator[SymbolToStringTag] === "Array Iterator";
+ return (
+ value[SymbolIterator] === NativeArrayPrototypeSymbolIterator &&
+ ArrayIteratorPrototype.next === ArrayIteratorPrototypeNext
+ );
}
function isOrdinaryNativeTypedArray(value) {
if (!isNativeTypedArray(value)) {
return false;
}
- if (value[SymbolIterator] === NativeTypedArrayPrototypeSymbolIterator) {
- return true;
- }
- const iterator = value[SymbolIterator]();
- return iterator[SymbolToStringTag] === "Array Iterator";
+ return (
+ value[SymbolIterator] === NativeTypedArrayPrototypeSymbolIterator &&
+ ArrayIteratorPrototype.next === ArrayIteratorPrototypeNext
+ );
}
function isCanonicalIntegerIndexString(value) {
if (typeof value !== "string") {
@@ -307,11 +325,37 @@ function hasFloat16ArrayBrand(target) {
return ReflectHas(constructor, brand);
}
+const INVERSE_OF_EPSILON = 1 / EPSILON;
+function roundTiesToEven(num) {
+ return (num + INVERSE_OF_EPSILON) - INVERSE_OF_EPSILON;
+}
+const FLOAT16_MIN_VALUE = 6.103515625e-05;
+const FLOAT16_MAX_VALUE = 65504;
+const FLOAT16_EPSILON = 0.0009765625;
+const FLOAT16_EPSILON_MULTIPLIED_BY_FLOAT16_MIN_VALUE = FLOAT16_EPSILON * FLOAT16_MIN_VALUE;
+const FLOAT16_EPSILON_DEVIDED_BY_EPSILON = FLOAT16_EPSILON * INVERSE_OF_EPSILON;
+function roundToFloat16(num) {
+ const number = +num;
+ if (!NumberIsFinite(number) || number === 0) {
+ return number;
+ }
+ const sign = number > 0 ? 1 : -1;
+ const absolute = MathAbs(number);
+ if (absolute < FLOAT16_MIN_VALUE) {
+ return sign * roundTiesToEven(absolute / FLOAT16_EPSILON_MULTIPLIED_BY_FLOAT16_MIN_VALUE) * FLOAT16_EPSILON_MULTIPLIED_BY_FLOAT16_MIN_VALUE;
+ }
+ const temp = (1 + FLOAT16_EPSILON_DEVIDED_BY_EPSILON) * absolute;
+ const result = temp - (temp - absolute);
+ if (result > FLOAT16_MAX_VALUE || NumberIsNaN(result)) {
+ return sign * Infinity;
+ }
+ return sign * result;
+}
const buffer = new NativeArrayBuffer(4);
const floatView = new NativeFloat32Array(buffer);
const uint32View = new NativeUint32Array(buffer);
-const baseTable = new NativeUint32Array(512);
-const shiftTable = new NativeUint32Array(512);
+const baseTable = new NativeUint16Array(512);
+const shiftTable = new NativeUint8Array(512);
for (let i = 0; i < 256; ++i) {
const e = i - 127;
if (e < -27) {
@@ -342,18 +386,16 @@ for (let i = 0; i < 256; ++i) {
}
}
function roundToFloat16Bits(num) {
- floatView[0] = (num);
+ floatView[0] = roundToFloat16(num);
const f = uint32View[0];
const e = (f >> 23) & 0x1ff;
return baseTable[e] + ((f & 0x007fffff) >> shiftTable[e]);
}
const mantissaTable = new NativeUint32Array(2048);
-const exponentTable = new NativeUint32Array(64);
-const offsetTable = new NativeUint32Array(64);
for (let i = 1; i < 1024; ++i) {
let m = i << 13;
let e = 0;
- while((m & 0x00800000) === 0) {
+ while ((m & 0x00800000) === 0) {
m <<= 1;
e -= 0x00800000;
}
@@ -364,6 +406,7 @@ for (let i = 1; i < 1024; ++i) {
for (let i = 1024; i < 2048; ++i) {
mantissaTable[i] = 0x38000000 + ((i - 1024) << 13);
}
+const exponentTable = new NativeUint32Array(64);
for (let i = 1; i < 31; ++i) {
exponentTable[i] = i << 23;
}
@@ -373,14 +416,15 @@ for (let i = 33; i < 63; ++i) {
exponentTable[i] = 0x80000000 + ((i - 32) << 23);
}
exponentTable[63] = 0xc7800000;
+const offsetTable = new NativeUint16Array(64);
for (let i = 1; i < 64; ++i) {
if (i !== 32) {
offsetTable[i] = 1024;
}
}
function convertToNumber(float16bits) {
- const m = float16bits >> 10;
- uint32View[0] = mantissaTable[offsetTable[m] + (float16bits & 0x3ff)] + exponentTable[m];
+ const i = float16bits >> 10;
+ uint32View[0] = mantissaTable[offsetTable[i] + (float16bits & 0x3ff)] + exponentTable[i];
return floatView[0];
}
@@ -572,26 +616,20 @@ class Float16Array {
let float16bitsArray;
if (isFloat16Array(input)) {
float16bitsArray = ReflectConstruct(NativeUint16Array, [getFloat16BitsArray(input)], new.target);
- } else if (isObject(input) && !isArrayBuffer(input)) {
+ } else if (isObject(input) && !isAnyArrayBuffer(input)) {
let list;
let length;
if (isNativeTypedArray(input)) {
list = input;
length = TypedArrayPrototypeGetLength(input);
const buffer = TypedArrayPrototypeGetBuffer(input);
- const BufferConstructor = !isSharedArrayBuffer(buffer)
- ? (SpeciesConstructor(
- buffer,
- NativeArrayBuffer
- ))
- : NativeArrayBuffer;
if (IsDetachedBuffer(buffer)) {
throw NativeTypeError(ATTEMPTING_TO_ACCESS_DETACHED_ARRAYBUFFER);
}
if (isNativeBigIntTypedArray(input)) {
throw NativeTypeError(CANNOT_MIX_BIGINT_AND_OTHER_TYPES);
}
- const data = new BufferConstructor(
+ const data = new NativeArrayBuffer(
length * BYTES_PER_ELEMENT
);
float16bitsArray = ReflectConstruct(NativeUint16Array, [data], new.target);
@@ -758,6 +796,30 @@ class Float16Array {
}
return convertToNumber(float16bitsArray[k]);
}
+ with(index, value) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const length = TypedArrayPrototypeGetLength(float16bitsArray);
+ const relativeIndex = ToIntegerOrInfinity(index);
+ const k = relativeIndex >= 0 ? relativeIndex : length + relativeIndex;
+ const number = +value;
+ if (k < 0 || k >= length) {
+ throw NativeRangeError(OFFSET_IS_OUT_OF_BOUNDS);
+ }
+ const uint16 = new NativeUint16Array(
+ TypedArrayPrototypeGetBuffer(float16bitsArray),
+ TypedArrayPrototypeGetByteOffset(float16bitsArray),
+ TypedArrayPrototypeGetLength(float16bitsArray)
+ );
+ const cloned = new Float16Array(
+ TypedArrayPrototypeGetBuffer(
+ TypedArrayPrototypeSlice(uint16)
+ )
+ );
+ const array = getFloat16BitsArray(cloned);
+ array[k] = roundToFloat16Bits(number);
+ return cloned;
+ }
map(callback, ...opts) {
assertFloat16Array(this);
const float16bitsArray = getFloat16BitsArray(this);
@@ -995,6 +1057,23 @@ class Float16Array {
TypedArrayPrototypeReverse(float16bitsArray);
return this;
}
+ toReversed() {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ const uint16 = new NativeUint16Array(
+ TypedArrayPrototypeGetBuffer(float16bitsArray),
+ TypedArrayPrototypeGetByteOffset(float16bitsArray),
+ TypedArrayPrototypeGetLength(float16bitsArray)
+ );
+ const cloned = new Float16Array(
+ TypedArrayPrototypeGetBuffer(
+ TypedArrayPrototypeSlice(uint16)
+ )
+ );
+ const clonedFloat16bitsArray = getFloat16BitsArray(cloned);
+ TypedArrayPrototypeReverse(clonedFloat16bitsArray);
+ return cloned;
+ }
fill(value, ...opts) {
assertFloat16Array(this);
const float16bitsArray = getFloat16BitsArray(this);
@@ -1020,6 +1099,29 @@ class Float16Array {
});
return this;
}
+ toSorted(compareFn) {
+ assertFloat16Array(this);
+ const float16bitsArray = getFloat16BitsArray(this);
+ if (compareFn !== undefined && typeof compareFn !== "function") {
+ throw new NativeTypeError(THE_COMPARISON_FUNCTION_MUST_BE_EITHER_A_FUNCTION_OR_UNDEFINED);
+ }
+ const sortCompare = compareFn !== undefined ? compareFn : defaultCompare;
+ const uint16 = new NativeUint16Array(
+ TypedArrayPrototypeGetBuffer(float16bitsArray),
+ TypedArrayPrototypeGetByteOffset(float16bitsArray),
+ TypedArrayPrototypeGetLength(float16bitsArray)
+ );
+ const cloned = new Float16Array(
+ TypedArrayPrototypeGetBuffer(
+ TypedArrayPrototypeSlice(uint16)
+ )
+ );
+ const clonedFloat16bitsArray = getFloat16BitsArray(cloned);
+ TypedArrayPrototypeSort(clonedFloat16bitsArray, (x, y) => {
+ return sortCompare(convertToNumber(x), convertToNumber(y));
+ });
+ return cloned;
+ }
slice(start, end) {
assertFloat16Array(this);
const float16bitsArray = getFloat16BitsArray(this);
@@ -1216,13 +1318,8 @@ function setFloat16(dataView, byteOffset, value, ...opts) {
);
}
-function hfround(x) {
- const number = +x;
- if (!NumberIsFinite(number) || number === 0) {
- return number;
- }
- const x16 = roundToFloat16Bits(number);
- return convertToNumber(x16);
+function f16round(x) {
+ return roundToFloat16(x);
}
-export { Float16Array, getFloat16, hfround, isFloat16Array, isTypedArray, setFloat16 };
+export { Float16Array, f16round, getFloat16, f16round as hfround, isFloat16Array, isTypedArray, setFloat16 }; \ No newline at end of file
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/README.md b/dom/webgpu/tests/cts/checkout/src/resources/README.md
index 824f82b998..a1ed060417 100644
--- a/dom/webgpu/tests/cts/checkout/src/resources/README.md
+++ b/dom/webgpu/tests/cts/checkout/src/resources/README.md
@@ -2,14 +2,92 @@ Always use `getResourcePath()` to get the appropriate path to these resources de
on the context (WPT, standalone, worker, etc.)
-The test video files were generated with the ffmpeg cmds below:
-ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx -pix_fmt yuv420p -frames 500 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv four-colors-vp8-bt601.webm
-ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libtheora -pix_fmt yuv420p -frames 500 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv four-colors-theora-bt601.ogv
-ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libx264 -pix_fmt yuv420p -frames 500 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv four-colors-h264-bt601.mp4
-ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 500 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv four-colors-vp9-bt601.webm
-ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 500 -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vf scale=out_color_matrix=bt709:out_range=tv four-colors-vp9-bt709.webm
-
-These rotation test files are copies of four-colors-h264-bt601.mp4 with metadata changes.
-ffmpeg.exe -i .\four-colors-h264-bt601.mp4 -c copy -metadata:s:v rotate=90 four-colors-h264-bt601-rotate-90.mp4
-ffmpeg.exe -i .\four-colors-h264-bt601.mp4 -c copy -metadata:s:v rotate=180 four-colors-h264-bt601-rotate-180.mp4
-ffmpeg.exe -i .\four-colors-h264-bt601.mp4 -c copy -metadata:s:v rotate=270 four-colors-h264-bt601-rotate-270.mp4 \ No newline at end of file
+The test video files were generated with by ffmpeg cmds below:
+```
+// Generate four-colors-vp8-bt601.webm, mimeType: 'video/webm; codecs=vp8'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv four-colors-vp8-bt601.webm
+
+// Generate four-colors-h264-bt601.mp4, mimeType: 'video/mp4; codecs=avc1.4d400c'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libx264 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv four-colors-h264-bt601.mp4
+
+// Generate four-colors-vp9-bt601.webm, mimeType: 'video/webm; codecs=vp9'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv four-colors-vp9-bt601.webm
+
+// Generate four-colors-vp9-bt709.webm, mimeType: 'video/webm; codecs=vp9'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 50 -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vf scale=out_color_matrix=bt709:out_range=tv four-colors-vp9-bt709.webm
+
+// Generate four-colors-vp9-bt601.mp4, mimeType: 'video/mp4; codecs=vp9'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv four-colors-vp9-bt601.mp4
+```
+
+Generate video files to test rotation behaviour.
+Use ffmepg to rotate video content x degrees in cw direction (by using `transpose`) and update transform matrix in metadata through `display_rotation` to x degrees to apply ccw direction rotation.
+
+H264 rotated video files are generated by ffmpeg cmds below:
+```
+// Generate four-colors-h264-bt601-rotate-90.mp4, mimeType: 'video/mp4; codecs=avc1.4d400c'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libx264 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv -vf transpose=2 temp.mp4
+ffmpeg -display_rotation 270 -i temp.mp4 -c copy four-colors-h264-bt601-rotate-90.mp4
+rm temp.mp4
+
+// Generate four-colors-h264-bt601-rotate-180.mp4, mimeType: 'video/mp4; codecs=avc1.4d400c'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libx264 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv -vf transpose=2,transpose=2 temp.mp4
+ffmpeg -display_rotation 180 -i temp.mp4 -c copy four-colors-h264-bt601-rotate-180.mp4
+rm temp.mp4
+
+// Generate four-colors-h264-bt601-rotate-270.mp4, mimeType: 'video/mp4; codecs=avc1.4d400c'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libx264 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv -vf transpose=1 temp.mp4
+ffmpeg -display_rotation 90 -i temp.mp4 -c copy four-colors-h264-bt601-rotate-270.mp4
+rm temp.mp4
+
+```
+
+Vp9 rotated video files are generated by ffmpeg cmds below:
+```
+// Generate four-colors-vp9-bt601-rotate-90.mp4, mimeType: 'video/mp4; codecs=vp9'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv -vf transpose=2 temp.mp4
+ffmpeg -display_rotation 270 -i temp.mp4 -c copy four-colors-vp9-bt601-rotate-90.mp4
+rm temp.mp4
+
+// Generate four-colors-vp9-bt601-rotate-180.mp4, mimeType: 'video/mp4; codecs=vp9'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv -vf transpose=2,transpose=2 temp.mp4
+ffmpeg -display_rotation 180 -i temp.mp4 -c copy four-colors-vp9-bt601-rotate-180.mp4
+rm temp.mp4
+
+// Generate four-colors-vp9-bt601-rotate-270.mp4, mimeType: 'video/mp4; codecs=vp9'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv -vf transpose=1 temp.mp4
+ffmpeg -display_rotation 90 -i temp.mp4 -c copy four-colors-vp9-bt601-rotate-270.mp4
+rm temp.mp4
+
+```
+
+Generate video files to test flip behaviour.
+Use ffmpeg to flip video content. Using `display_hflip` to do horizontal flip and `display_vflip` to do vertical flip.
+
+H264 flip video files are generated by ffmpeg cmds below:
+```
+// Generate four-colors-h264-bt601-hflip.mp4, mimeType: 'video/mp4; codecs=avc1.4d400c'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libx264 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv temp.mp4
+ffmpeg -display_hflip -i temp.mp4 -c copy four-colors-h264-bt601-hflip.mp4
+rm temp.mp4
+
+// Generate four-colors-h264-bt601-vflip.mp4, mimeType: 'video/mp4; codecs=avc1.4d400c'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libx264 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv temp.mp4
+ffmpeg -display_vflip -i temp.mp4 -c copy four-colors-h264-bt601-vflip.mp4
+rm temp.mp4
+
+```
+
+Vp9 flip video files are generated by ffmpeg cmds below:
+```
+// Generate four-colors-vp9-bt601-hflip.mp4, mimeType: 'video/mp4; codecs=vp09.00.10.08'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv temp.mp4
+ffmpeg -display_hflip -i temp.mp4 -c copy four-colors-vp9-bt601-hflip.mp4
+rm temp.mp4
+
+// Generate four-colors-vp9-bt601-vflip.mp4, mimeType: 'video/mp4; codecs=vp09.00.10.08'
+ffmpeg.exe -loop 1 -i .\four-colors.png -c:v libvpx-vp9 -pix_fmt yuv420p -frames 50 -colorspace smpte170m -color_primaries smpte170m -color_trc smpte170m -color_range tv temp.mp4
+ffmpeg -display_vflip -i temp.mp4 -c copy four-colors-vp9-bt601-vflip.mp4
+rm temp.mp4
+
+```
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/hashes.json b/dom/webgpu/tests/cts/checkout/src/resources/cache/hashes.json
new file mode 100644
index 0000000000..90efb474ad
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/hashes.json
@@ -0,0 +1,111 @@
+{
+ "webgpu/shader/execution/binary/af_addition.bin": "17c26b18",
+ "webgpu/shader/execution/binary/af_logical.bin": "b4fdda88",
+ "webgpu/shader/execution/binary/af_division.bin": "fa1fc451",
+ "webgpu/shader/execution/binary/af_matrix_addition.bin": "a2bebfc0",
+ "webgpu/shader/execution/binary/af_matrix_subtraction.bin": "5456944c",
+ "webgpu/shader/execution/binary/af_multiplication.bin": "fc54cae0",
+ "webgpu/shader/execution/binary/af_remainder.bin": "2ee1a014",
+ "webgpu/shader/execution/binary/af_subtraction.bin": "ba82dab3",
+ "webgpu/shader/execution/binary/f16_addition.bin": "4ccf0cde",
+ "webgpu/shader/execution/binary/f16_logical.bin": "5ffb4769",
+ "webgpu/shader/execution/binary/f16_division.bin": "c69d5326",
+ "webgpu/shader/execution/binary/f16_matrix_addition.bin": "23006f90",
+ "webgpu/shader/execution/binary/f16_matrix_matrix_multiplication.bin": "3b581360",
+ "webgpu/shader/execution/binary/f16_matrix_scalar_multiplication.bin": "4c2cf2fa",
+ "webgpu/shader/execution/binary/f16_matrix_subtraction.bin": "902ffcbc",
+ "webgpu/shader/execution/binary/f16_matrix_vector_multiplication.bin": "48acf022",
+ "webgpu/shader/execution/binary/f16_multiplication.bin": "e83fedc6",
+ "webgpu/shader/execution/binary/f16_remainder.bin": "68178090",
+ "webgpu/shader/execution/binary/f16_subtraction.bin": "de63294a",
+ "webgpu/shader/execution/binary/f32_addition.bin": "d693585",
+ "webgpu/shader/execution/binary/f32_logical.bin": "57b9e9da",
+ "webgpu/shader/execution/binary/f32_division.bin": "79048538",
+ "webgpu/shader/execution/binary/f32_matrix_addition.bin": "301c58ba",
+ "webgpu/shader/execution/binary/f32_matrix_matrix_multiplication.bin": "41bae804",
+ "webgpu/shader/execution/binary/f32_matrix_scalar_multiplication.bin": "d8a1003a",
+ "webgpu/shader/execution/binary/f32_matrix_subtraction.bin": "3b1cc190",
+ "webgpu/shader/execution/binary/f32_matrix_vector_multiplication.bin": "66a57dbc",
+ "webgpu/shader/execution/binary/f32_multiplication.bin": "c0bf07da",
+ "webgpu/shader/execution/binary/f32_remainder.bin": "238fd8eb",
+ "webgpu/shader/execution/binary/f32_subtraction.bin": "d977904f",
+ "webgpu/shader/execution/binary/i32_arithmetic.bin": "8168bdb4",
+ "webgpu/shader/execution/binary/i32_comparison.bin": "eae9d767",
+ "webgpu/shader/execution/binary/u32_arithmetic.bin": "5c313ea9",
+ "webgpu/shader/execution/binary/u32_comparison.bin": "5ef80f48",
+ "webgpu/shader/execution/abs.bin": "1a23882d",
+ "webgpu/shader/execution/acos.bin": "66020d3b",
+ "webgpu/shader/execution/acosh.bin": "eeed5b15",
+ "webgpu/shader/execution/asin.bin": "e38b87bf",
+ "webgpu/shader/execution/asinh.bin": "d5cf509e",
+ "webgpu/shader/execution/atan.bin": "780f5bf9",
+ "webgpu/shader/execution/atan2.bin": "242c3f80",
+ "webgpu/shader/execution/atanh.bin": "2c6771e5",
+ "webgpu/shader/execution/bitcast.bin": "4ebd46da",
+ "webgpu/shader/execution/ceil.bin": "b190ce63",
+ "webgpu/shader/execution/clamp.bin": "9ba5f3c9",
+ "webgpu/shader/execution/cos.bin": "edeb923",
+ "webgpu/shader/execution/cosh.bin": "c12c748b",
+ "webgpu/shader/execution/cross.bin": "4cfaddf8",
+ "webgpu/shader/execution/degrees.bin": "dc77e03b",
+ "webgpu/shader/execution/determinant.bin": "a87e4d61",
+ "webgpu/shader/execution/distance.bin": "9e397f5a",
+ "webgpu/shader/execution/dot.bin": "db692304",
+ "webgpu/shader/execution/exp.bin": "b0cbd306",
+ "webgpu/shader/execution/exp2.bin": "b32745cd",
+ "webgpu/shader/execution/faceForward.bin": "f0cc892a",
+ "webgpu/shader/execution/floor.bin": "4a460013",
+ "webgpu/shader/execution/fma.bin": "c89c3d19",
+ "webgpu/shader/execution/fract.bin": "f6230a96",
+ "webgpu/shader/execution/frexp.bin": "132962c",
+ "webgpu/shader/execution/inverseSqrt.bin": "cc1b943c",
+ "webgpu/shader/execution/ldexp.bin": "4e14b67d",
+ "webgpu/shader/execution/length.bin": "a75b23ef",
+ "webgpu/shader/execution/log.bin": "61175a12",
+ "webgpu/shader/execution/log2.bin": "d24b375d",
+ "webgpu/shader/execution/max.bin": "5689d61b",
+ "webgpu/shader/execution/min.bin": "8fd8d393",
+ "webgpu/shader/execution/mix.bin": "caf44b85",
+ "webgpu/shader/execution/modf.bin": "223ff03f",
+ "webgpu/shader/execution/normalize.bin": "e0634ba",
+ "webgpu/shader/execution/pack2x16float.bin": "a8ca6b51",
+ "webgpu/shader/execution/pow.bin": "b2bbd5ce",
+ "webgpu/shader/execution/quantizeToF16.bin": "be9ef6ab",
+ "webgpu/shader/execution/radians.bin": "afaf4f61",
+ "webgpu/shader/execution/reflect.bin": "742b05b4",
+ "webgpu/shader/execution/refract.bin": "ad05949f",
+ "webgpu/shader/execution/round.bin": "3a006c0c",
+ "webgpu/shader/execution/saturate.bin": "61753b4f",
+ "webgpu/shader/execution/sign.bin": "71e6517c",
+ "webgpu/shader/execution/sin.bin": "17c44cc8",
+ "webgpu/shader/execution/sinh.bin": "151ae2d1",
+ "webgpu/shader/execution/smoothstep.bin": "35db8f9a",
+ "webgpu/shader/execution/sqrt.bin": "a4b4bdf9",
+ "webgpu/shader/execution/step.bin": "e3cc0f86",
+ "webgpu/shader/execution/tan.bin": "9ad0e1f1",
+ "webgpu/shader/execution/tanh.bin": "99454fdd",
+ "webgpu/shader/execution/transpose.bin": "959a2407",
+ "webgpu/shader/execution/trunc.bin": "1a47263e",
+ "webgpu/shader/execution/unpack2x16float.bin": "39f959fe",
+ "webgpu/shader/execution/unpack2x16snorm.bin": "b409d047",
+ "webgpu/shader/execution/unpack2x16unorm.bin": "49a89145",
+ "webgpu/shader/execution/unpack4x8snorm.bin": "e53a7842",
+ "webgpu/shader/execution/unpack4x8unorm.bin": "9bc3f0ea",
+ "webgpu/shader/execution/unary/af_arithmetic.bin": "e9e06bc7",
+ "webgpu/shader/execution/unary/af_assignment.bin": "5c9160c2",
+ "webgpu/shader/execution/unary/bool_conversion.bin": "a2260d3a",
+ "webgpu/shader/execution/unary/f16_arithmetic.bin": "7a3fd3db",
+ "webgpu/shader/execution/unary/f16_conversion.bin": "b9407945",
+ "webgpu/shader/execution/unary/f32_arithmetic.bin": "6e392701",
+ "webgpu/shader/execution/unary/f32_conversion.bin": "6ae4cce9",
+ "webgpu/shader/execution/unary/i32_arithmetic.bin": "f5ef6485",
+ "webgpu/shader/execution/unary/i32_conversion.bin": "75435733",
+ "webgpu/shader/execution/unary/u32_conversion.bin": "26baf99",
+ "webgpu/shader/execution/unary/ai_assignment.bin": "d1b00a8",
+ "webgpu/shader/execution/binary/ai_arithmetic.bin": "dc777f4e",
+ "webgpu/shader/execution/unary/ai_arithmetic.bin": "272c5df2",
+ "webgpu/shader/execution/binary/af_matrix_matrix_multiplication.bin": "ab6db19f",
+ "webgpu/shader/execution/binary/af_matrix_scalar_multiplication.bin": "1de2ec75",
+ "webgpu/shader/execution/binary/af_matrix_vector_multiplication.bin": "e665650a",
+ "webgpu/shader/execution/derivatives.bin": "899e4e4c"
+} \ No newline at end of file
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/abs.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/abs.bin
new file mode 100644
index 0000000000..4cba9b72df
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/abs.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/acos.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/acos.bin
new file mode 100644
index 0000000000..2ecaaa389a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/acos.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/acosh.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/acosh.bin
new file mode 100644
index 0000000000..d48659f3c3
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/acosh.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/asin.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/asin.bin
new file mode 100644
index 0000000000..b199953eaf
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/asin.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/asinh.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/asinh.bin
new file mode 100644
index 0000000000..b370c53b01
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/asinh.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atan.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atan.bin
new file mode 100644
index 0000000000..6ab0ba106a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atan.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atan2.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atan2.bin
new file mode 100644
index 0000000000..0109ddbc37
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atan2.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atanh.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atanh.bin
new file mode 100644
index 0000000000..e6a190b35d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/atanh.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_addition.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_addition.bin
new file mode 100644
index 0000000000..ebd757c1b6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_addition.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_division.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_division.bin
new file mode 100644
index 0000000000..656356d32e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_division.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_logical.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_logical.bin
new file mode 100644
index 0000000000..e3594458f2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_logical.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_addition.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_addition.bin
new file mode 100644
index 0000000000..ba9d123cbf
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_addition.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_matrix_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_matrix_multiplication.bin
new file mode 100644
index 0000000000..58d0d40cb9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_matrix_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_scalar_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_scalar_multiplication.bin
new file mode 100644
index 0000000000..c8a3b7205a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_scalar_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_subtraction.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_subtraction.bin
new file mode 100644
index 0000000000..8f88d196ce
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_subtraction.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_vector_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_vector_multiplication.bin
new file mode 100644
index 0000000000..545a5112cf
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_matrix_vector_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_multiplication.bin
new file mode 100644
index 0000000000..552d8b4892
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_remainder.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_remainder.bin
new file mode 100644
index 0000000000..c45792abf4
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_remainder.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_subtraction.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_subtraction.bin
new file mode 100644
index 0000000000..6f0be29785
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/af_subtraction.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/ai_arithmetic.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/ai_arithmetic.bin
new file mode 100644
index 0000000000..658eb46d39
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/ai_arithmetic.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_addition.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_addition.bin
new file mode 100644
index 0000000000..30f099139d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_addition.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_division.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_division.bin
new file mode 100644
index 0000000000..22e60bef81
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_division.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_logical.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_logical.bin
new file mode 100644
index 0000000000..932af58208
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_logical.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_addition.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_addition.bin
new file mode 100644
index 0000000000..452376760b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_addition.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_matrix_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_matrix_multiplication.bin
new file mode 100644
index 0000000000..e823daac6c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_matrix_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_scalar_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_scalar_multiplication.bin
new file mode 100644
index 0000000000..b48be81ebd
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_scalar_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_subtraction.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_subtraction.bin
new file mode 100644
index 0000000000..386558c3f7
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_subtraction.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_vector_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_vector_multiplication.bin
new file mode 100644
index 0000000000..cbf224a6b6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_matrix_vector_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_multiplication.bin
new file mode 100644
index 0000000000..e9d27019a3
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_remainder.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_remainder.bin
new file mode 100644
index 0000000000..d21370aec9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_remainder.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_subtraction.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_subtraction.bin
new file mode 100644
index 0000000000..97080a8ce5
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f16_subtraction.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_addition.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_addition.bin
new file mode 100644
index 0000000000..be7b997bd8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_addition.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_division.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_division.bin
new file mode 100644
index 0000000000..f80461c21b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_division.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_logical.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_logical.bin
new file mode 100644
index 0000000000..a819eab08c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_logical.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_addition.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_addition.bin
new file mode 100644
index 0000000000..b1d7179264
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_addition.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_matrix_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_matrix_multiplication.bin
new file mode 100644
index 0000000000..232760828a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_matrix_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_scalar_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_scalar_multiplication.bin
new file mode 100644
index 0000000000..76f867b959
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_scalar_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_subtraction.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_subtraction.bin
new file mode 100644
index 0000000000..0d0fd2460d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_subtraction.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_vector_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_vector_multiplication.bin
new file mode 100644
index 0000000000..e139fc9713
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_matrix_vector_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_multiplication.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_multiplication.bin
new file mode 100644
index 0000000000..1837ce922c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_multiplication.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_remainder.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_remainder.bin
new file mode 100644
index 0000000000..3febfca1d1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_remainder.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_subtraction.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_subtraction.bin
new file mode 100644
index 0000000000..32b34f690c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/f32_subtraction.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/i32_arithmetic.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/i32_arithmetic.bin
new file mode 100644
index 0000000000..bacd4c0c54
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/i32_arithmetic.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/i32_comparison.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/i32_comparison.bin
new file mode 100644
index 0000000000..d5a745e85c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/i32_comparison.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/u32_arithmetic.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/u32_arithmetic.bin
new file mode 100644
index 0000000000..56ef292864
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/u32_arithmetic.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/u32_comparison.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/u32_comparison.bin
new file mode 100644
index 0000000000..5ba639b3cd
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/binary/u32_comparison.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/bitcast.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/bitcast.bin
new file mode 100644
index 0000000000..2acc4a318b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/bitcast.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/ceil.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/ceil.bin
new file mode 100644
index 0000000000..9b93ed416f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/ceil.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/clamp.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/clamp.bin
new file mode 100644
index 0000000000..492be017aa
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/clamp.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cos.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cos.bin
new file mode 100644
index 0000000000..4e34eff3f1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cos.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cosh.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cosh.bin
new file mode 100644
index 0000000000..5b30d2786c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cosh.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cross.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cross.bin
new file mode 100644
index 0000000000..c8ee9d3e1a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/cross.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/degrees.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/degrees.bin
new file mode 100644
index 0000000000..662558d78a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/degrees.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/derivatives.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/derivatives.bin
new file mode 100644
index 0000000000..d6d0788775
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/derivatives.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/determinant.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/determinant.bin
new file mode 100644
index 0000000000..16d58c6db6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/determinant.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/distance.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/distance.bin
new file mode 100644
index 0000000000..23a4756a69
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/distance.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/dot.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/dot.bin
new file mode 100644
index 0000000000..13622a686b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/dot.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/exp.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/exp.bin
new file mode 100644
index 0000000000..29361a2b27
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/exp.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/exp2.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/exp2.bin
new file mode 100644
index 0000000000..367b5a8e90
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/exp2.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/faceForward.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/faceForward.bin
new file mode 100644
index 0000000000..8f065bb97c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/faceForward.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/floor.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/floor.bin
new file mode 100644
index 0000000000..b5341907f8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/floor.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/fma.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/fma.bin
new file mode 100644
index 0000000000..eb4cb9ebbe
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/fma.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/fract.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/fract.bin
new file mode 100644
index 0000000000..f889961d8f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/fract.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/frexp.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/frexp.bin
new file mode 100644
index 0000000000..6811dfa295
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/frexp.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/inverseSqrt.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/inverseSqrt.bin
new file mode 100644
index 0000000000..5039345ad0
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/inverseSqrt.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/ldexp.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/ldexp.bin
new file mode 100644
index 0000000000..bab78ed2af
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/ldexp.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/length.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/length.bin
new file mode 100644
index 0000000000..3644d9b683
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/length.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/log.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/log.bin
new file mode 100644
index 0000000000..ba591faad8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/log.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/log2.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/log2.bin
new file mode 100644
index 0000000000..00641ce119
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/log2.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/max.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/max.bin
new file mode 100644
index 0000000000..3861a94aca
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/max.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/min.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/min.bin
new file mode 100644
index 0000000000..21c29e62ed
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/min.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/mix.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/mix.bin
new file mode 100644
index 0000000000..c42b2aa067
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/mix.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/modf.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/modf.bin
new file mode 100644
index 0000000000..363cc161fd
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/modf.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/normalize.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/normalize.bin
new file mode 100644
index 0000000000..01b8eab700
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/normalize.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/pack2x16float.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/pack2x16float.bin
new file mode 100644
index 0000000000..e95227d36e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/pack2x16float.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/pow.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/pow.bin
new file mode 100644
index 0000000000..4f5faf3293
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/pow.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/quantizeToF16.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/quantizeToF16.bin
new file mode 100644
index 0000000000..9e4308d5cd
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/quantizeToF16.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/radians.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/radians.bin
new file mode 100644
index 0000000000..f5285d1087
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/radians.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/reflect.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/reflect.bin
new file mode 100644
index 0000000000..30cd7ee925
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/reflect.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/refract.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/refract.bin
new file mode 100644
index 0000000000..c428285817
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/refract.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/round.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/round.bin
new file mode 100644
index 0000000000..c3b30b68f0
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/round.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/saturate.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/saturate.bin
new file mode 100644
index 0000000000..2e1eb821a9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/saturate.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sign.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sign.bin
new file mode 100644
index 0000000000..033f2e8158
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sign.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sin.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sin.bin
new file mode 100644
index 0000000000..a2ca632008
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sin.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sinh.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sinh.bin
new file mode 100644
index 0000000000..1176cd472b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sinh.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/smoothstep.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/smoothstep.bin
new file mode 100644
index 0000000000..73b65d17c2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/smoothstep.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sqrt.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sqrt.bin
new file mode 100644
index 0000000000..6dd8088c08
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/sqrt.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/step.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/step.bin
new file mode 100644
index 0000000000..f6c6c7b5f3
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/step.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/tan.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/tan.bin
new file mode 100644
index 0000000000..572bee4df2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/tan.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/tanh.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/tanh.bin
new file mode 100644
index 0000000000..a13028b165
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/tanh.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/transpose.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/transpose.bin
new file mode 100644
index 0000000000..d1b6bf04ee
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/transpose.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/trunc.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/trunc.bin
new file mode 100644
index 0000000000..ba81e2ada4
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/trunc.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/af_arithmetic.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/af_arithmetic.bin
new file mode 100644
index 0000000000..21d3d702ae
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/af_arithmetic.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/af_assignment.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/af_assignment.bin
new file mode 100644
index 0000000000..a92279b5ce
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/af_assignment.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/ai_arithmetic.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/ai_arithmetic.bin
new file mode 100644
index 0000000000..2fa273ff19
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/ai_arithmetic.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/ai_assignment.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/ai_assignment.bin
new file mode 100644
index 0000000000..7956b3652a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/ai_assignment.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/bool_conversion.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/bool_conversion.bin
new file mode 100644
index 0000000000..98a90ea45b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/bool_conversion.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f16_arithmetic.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f16_arithmetic.bin
new file mode 100644
index 0000000000..acf8a702ce
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f16_arithmetic.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f16_conversion.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f16_conversion.bin
new file mode 100644
index 0000000000..14299da766
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f16_conversion.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f32_arithmetic.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f32_arithmetic.bin
new file mode 100644
index 0000000000..ebc60029fa
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f32_arithmetic.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f32_conversion.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f32_conversion.bin
new file mode 100644
index 0000000000..bdcc0c7298
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/f32_conversion.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/i32_arithmetic.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/i32_arithmetic.bin
new file mode 100644
index 0000000000..4753b020c9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/i32_arithmetic.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/i32_conversion.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/i32_conversion.bin
new file mode 100644
index 0000000000..04841df607
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/i32_conversion.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/u32_conversion.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/u32_conversion.bin
new file mode 100644
index 0000000000..277ffc4d76
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unary/u32_conversion.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16float.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16float.bin
new file mode 100644
index 0000000000..7f06cb0df6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16float.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16snorm.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16snorm.bin
new file mode 100644
index 0000000000..08c6af9e93
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16snorm.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16unorm.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16unorm.bin
new file mode 100644
index 0000000000..1bb97b9c55
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack2x16unorm.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack4x8snorm.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack4x8snorm.bin
new file mode 100644
index 0000000000..1db9856b05
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack4x8snorm.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack4x8unorm.bin b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack4x8unorm.bin
new file mode 100644
index 0000000000..8d1f3dc7fb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/cache/webgpu/shader/execution/unpack4x8unorm.bin
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-hflip.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-hflip.mp4
new file mode 100644
index 0000000000..f83b4f9698
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-hflip.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-180.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-180.mp4
index 1f0e9094a5..6665ea900d 100644
--- a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-180.mp4
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-180.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-270.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-270.mp4
index e0480ceff2..b1e32bc83a 100644
--- a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-270.mp4
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-270.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-90.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-90.mp4
index 9a6261056e..66a98d0ed0 100644
--- a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-90.mp4
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-rotate-90.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-vflip.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-vflip.mp4
new file mode 100644
index 0000000000..90c3297a9a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601-vflip.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601.mp4
index 81a5ade435..5317bbf7c6 100644
--- a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601.mp4
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-h264-bt601.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-theora-bt601.ogv b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-theora-bt601.ogv
deleted file mode 100644
index 79ed41163c..0000000000
--- a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-theora-bt601.ogv
+++ /dev/null
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp8-bt601.webm b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp8-bt601.webm
index 20a2178596..d1504ee332 100644
--- a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp8-bt601.webm
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp8-bt601.webm
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-hflip.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-hflip.mp4
new file mode 100644
index 0000000000..f782c32651
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-hflip.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-180.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-180.mp4
new file mode 100644
index 0000000000..fc712becd7
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-180.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-270.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-270.mp4
new file mode 100644
index 0000000000..a83558f53c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-270.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-90.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-90.mp4
new file mode 100644
index 0000000000..73a03795ba
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-rotate-90.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-vflip.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-vflip.mp4
new file mode 100644
index 0000000000..c9de14696a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601-vflip.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601.mp4 b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601.mp4
new file mode 100644
index 0000000000..0d8d4f829c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601.mp4
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601.webm b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601.webm
index a4044a9209..47a43a0695 100644
--- a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601.webm
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt601.webm
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt709.webm b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt709.webm
index 189e422035..a9e069ee1c 100644
--- a/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt709.webm
+++ b/dom/webgpu/tests/cts/checkout/src/resources/four-colors-vp9-bt709.webm
Binary files differ
diff --git a/dom/webgpu/tests/cts/checkout/src/unittests/conversion.spec.ts b/dom/webgpu/tests/cts/checkout/src/unittests/conversion.spec.ts
index 8606aa8717..e144f39288 100644
--- a/dom/webgpu/tests/cts/checkout/src/unittests/conversion.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/unittests/conversion.spec.ts
@@ -18,7 +18,7 @@ import {
i32,
kFloat16Format,
kFloat32Format,
- Matrix,
+ MatrixValue,
numbersApproximatelyEqual,
pack2x16float,
pack2x16snorm,
@@ -26,14 +26,14 @@ import {
pack4x8snorm,
pack4x8unorm,
packRGB9E5UFloat,
- Scalar,
+ ScalarValue,
toMatrix,
u32,
unpackRGB9E5UFloat,
vec2,
vec3,
vec4,
- Vector,
+ VectorValue,
} from '../webgpu/util/conversion.js';
import { UnitTest } from './unit_test.js';
@@ -191,7 +191,7 @@ g.test('floatBitsToULPFromZero,32').fn(t => {
});
g.test('scalarWGSL').fn(t => {
- const cases: Array<[Scalar, string]> = [
+ const cases: Array<[ScalarValue, string]> = [
[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
@@ -227,7 +227,7 @@ expect: ${expect}`
});
g.test('vectorWGSL').fn(t => {
- const cases: Array<[Vector, string]> = [
+ const cases: Array<[VectorValue, string]> = [
[vec2(f32(42.0), f32(24.0)), 'vec2(42.0f, 24.0f)'],
[vec2(f16Bits(0x5140), f16Bits(0x4e00)), 'vec2(42.0h, 24.0h)'],
[vec2(u32(42), u32(24)), 'vec2(42u, 24u)'],
@@ -261,7 +261,7 @@ expect: ${expect}`
});
g.test('matrixWGSL').fn(t => {
- const cases: Array<[Matrix, string]> = [
+ const cases: Array<[MatrixValue, string]> = [
[
toMatrix(
[
@@ -391,7 +391,7 @@ g.test('constructorMatrix')
return [...Array(rows).keys()].map(r => scalar_builder(c * cols + r));
});
- const got = new Matrix(elements);
+ const got = new MatrixValue(elements);
const got_type = got.type;
t.expect(
got_type.cols === cols,
diff --git a/dom/webgpu/tests/cts/checkout/src/unittests/crc32.spec.ts b/dom/webgpu/tests/cts/checkout/src/unittests/crc32.spec.ts
new file mode 100644
index 0000000000..5986823c8a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/unittests/crc32.spec.ts
@@ -0,0 +1,28 @@
+export const description = `
+Test for crc32 utility functions.
+`;
+
+import { makeTestGroup } from '../common/framework/test_group.js';
+import { crc32, toHexString } from '../common/util/crc32.js';
+
+import { UnitTest } from './unit_test.js';
+
+class F extends UnitTest {
+ test(content: string, expect: string): void {
+ const got = toHexString(crc32(content));
+ this.expect(
+ expect === got,
+ `
+expected: ${expect}
+got: ${got}`
+ );
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('strings').fn(t => {
+ t.test('', '00000000');
+ t.test('hello world', '0d4a1185');
+ t.test('123456789', 'cbf43926');
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/unittests/floating_point.spec.ts b/dom/webgpu/tests/cts/checkout/src/unittests/floating_point.spec.ts
index e8f8525d7f..31501f77ff 100644
--- a/dom/webgpu/tests/cts/checkout/src/unittests/floating_point.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/unittests/floating_point.spec.ts
@@ -5,7 +5,12 @@ Floating Point unit tests.
import { makeTestGroup } from '../common/framework/test_group.js';
import { objectEquals, unreachable } from '../common/util/util.js';
import { kValue } from '../webgpu/util/constants.js';
-import { FP, FPInterval, FPIntervalParam, IntervalBounds } from '../webgpu/util/floating_point.js';
+import {
+ FP,
+ FPInterval,
+ FPIntervalParam,
+ IntervalEndpoints,
+} from '../webgpu/util/floating_point.js';
import { map2DArray, oneULPF32, oneULPF16, oneULPF64 } from '../webgpu/util/math.js';
import {
reinterpretU16AsF16,
@@ -17,24 +22,19 @@ import { UnitTest } from './unit_test.js';
export const g = makeTestGroup(UnitTest);
-/**
- * For ULP purposes, abstract float behaves like f32, so need to swizzle it in
- * for expectations.
- */
const kFPTraitForULP = {
- abstract: 'f32',
f32: 'f32',
f16: 'f16',
} as const;
-/** Bounds indicating an expectation of unbounded error */
-const kUnboundedBounds: IntervalBounds = [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY];
+/** Endpoints indicating an expectation of unbounded error */
+const kUnboundedEndpoints: IntervalEndpoints = [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY];
-/** Interval from kUnboundedBounds */
+/** Interval from kUnboundedEndpoints */
const kUnboundedInterval = {
- f32: FP.f32.toParam(kUnboundedBounds),
- f16: FP.f16.toParam(kUnboundedBounds),
- abstract: FP.abstract.toParam(kUnboundedBounds),
+ f32: FP.f32.toParam(kUnboundedEndpoints),
+ f16: FP.f16.toParam(kUnboundedEndpoints),
+ abstract: FP.abstract.toParam(kUnboundedEndpoints),
};
/** @returns a number N * ULP greater than the provided number */
@@ -89,17 +89,17 @@ const kMinusOneULPFunctions = {
},
};
-/** @returns the expected IntervalBounds adjusted by the given error function
+/** @returns the expected IntervalEndpoints adjusted by the given error function
*
- * @param expected the bounds to be adjusted
- * @param error error function to adjust the bounds via
+ * @param expected the endpoints to be adjusted
+ * @param error error function to adjust the endpoints via
*/
function applyError(
- expected: number | IntervalBounds,
+ expected: number | IntervalEndpoints,
error: (n: number) => number
-): IntervalBounds {
+): IntervalEndpoints {
// Avoiding going through FPInterval to avoid tying this to a specific kind
- const unpack = (n: number | IntervalBounds): [number, number] => {
+ const unpack = (n: number | IntervalEndpoints): [number, number] => {
if (expected instanceof Array) {
switch (expected.length) {
case 1:
@@ -107,7 +107,7 @@ function applyError(
case 2:
return [expected[0], expected[1]];
}
- unreachable(`Tried to unpack an IntervalBounds with length other than 1 or 2`);
+ unreachable(`Tried to unpack an IntervalEndpoints with length other than 1 or 2`);
} else {
// TS doesn't narrow this to number automatically
return [n as number, n as number];
@@ -128,8 +128,8 @@ function applyError(
// FPInterval
interface ConstructorCase {
- input: IntervalBounds;
- expected: IntervalBounds;
+ input: IntervalEndpoints;
+ expected: IntervalEndpoints;
}
g.test('constructor')
@@ -160,7 +160,7 @@ g.test('constructor')
// Infinities
{ input: [0, constants.positive.infinity], expected: [0, Number.POSITIVE_INFINITY] },
{ input: [constants.negative.infinity, 0], expected: [Number.NEGATIVE_INFINITY, 0] },
- { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedBounds },
+ { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
];
// Note: Out of range values are limited to infinities for abstract float, due to abstract
@@ -182,13 +182,13 @@ g.test('constructor')
.fn(t => {
const i = new FPInterval(t.params.trait, ...t.params.input);
t.expect(
- objectEquals(i.bounds(), t.params.expected),
+ objectEquals(i.endpoints(), t.params.expected),
`new FPInterval('${t.params.trait}', [${t.params.input}]) returned ${i}. Expected [${t.params.expected}]`
);
});
interface ContainsNumberCase {
- bounds: number | IntervalBounds;
+ endpoints: number | IntervalEndpoints;
value: number;
expected: boolean;
}
@@ -203,90 +203,90 @@ g.test('contains_number')
// prettier-ignore
const cases: ContainsNumberCase[] = [
// Common usage
- { bounds: [0, 10], value: 0, expected: true },
- { bounds: [0, 10], value: 10, expected: true },
- { bounds: [0, 10], value: 5, expected: true },
- { bounds: [0, 10], value: -5, expected: false },
- { bounds: [0, 10], value: 50, expected: false },
- { bounds: [0, 10], value: Number.NaN, expected: false },
- { bounds: [-5, 10], value: 0, expected: true },
- { bounds: [-5, 10], value: 10, expected: true },
- { bounds: [-5, 10], value: 5, expected: true },
- { bounds: [-5, 10], value: -5, expected: true },
- { bounds: [-5, 10], value: -6, expected: false },
- { bounds: [-5, 10], value: 50, expected: false },
- { bounds: [-5, 10], value: -10, expected: false },
- { bounds: [-1.375, 2.5], value: -10, expected: false },
- { bounds: [-1.375, 2.5], value: 0.5, expected: true },
- { bounds: [-1.375, 2.5], value: 10, expected: false },
+ { endpoints: [0, 10], value: 0, expected: true },
+ { endpoints: [0, 10], value: 10, expected: true },
+ { endpoints: [0, 10], value: 5, expected: true },
+ { endpoints: [0, 10], value: -5, expected: false },
+ { endpoints: [0, 10], value: 50, expected: false },
+ { endpoints: [0, 10], value: Number.NaN, expected: false },
+ { endpoints: [-5, 10], value: 0, expected: true },
+ { endpoints: [-5, 10], value: 10, expected: true },
+ { endpoints: [-5, 10], value: 5, expected: true },
+ { endpoints: [-5, 10], value: -5, expected: true },
+ { endpoints: [-5, 10], value: -6, expected: false },
+ { endpoints: [-5, 10], value: 50, expected: false },
+ { endpoints: [-5, 10], value: -10, expected: false },
+ { endpoints: [-1.375, 2.5], value: -10, expected: false },
+ { endpoints: [-1.375, 2.5], value: 0.5, expected: true },
+ { endpoints: [-1.375, 2.5], value: 10, expected: false },
// Point
- { bounds: 0, value: 0, expected: true },
- { bounds: 0, value: 10, expected: false },
- { bounds: 0, value: -1000, expected: false },
- { bounds: 10, value: 10, expected: true },
- { bounds: 10, value: 0, expected: false },
- { bounds: 10, value: -10, expected: false },
- { bounds: 10, value: 11, expected: false },
+ { endpoints: 0, value: 0, expected: true },
+ { endpoints: 0, value: 10, expected: false },
+ { endpoints: 0, value: -1000, expected: false },
+ { endpoints: 10, value: 10, expected: true },
+ { endpoints: 10, value: 0, expected: false },
+ { endpoints: 10, value: -10, expected: false },
+ { endpoints: 10, value: 11, expected: false },
// Upper infinity
- { bounds: [0, constants.positive.infinity], value: constants.positive.min, expected: true },
- { bounds: [0, constants.positive.infinity], value: constants.positive.max, expected: true },
- { bounds: [0, constants.positive.infinity], value: constants.positive.infinity, expected: true },
- { bounds: [0, constants.positive.infinity], value: constants.negative.min, expected: false },
- { bounds: [0, constants.positive.infinity], value: constants.negative.max, expected: false },
- { bounds: [0, constants.positive.infinity], value: constants.negative.infinity, expected: false },
+ { endpoints: [0, constants.positive.infinity], value: constants.positive.min, expected: true },
+ { endpoints: [0, constants.positive.infinity], value: constants.positive.max, expected: true },
+ { endpoints: [0, constants.positive.infinity], value: constants.positive.infinity, expected: true },
+ { endpoints: [0, constants.positive.infinity], value: constants.negative.min, expected: false },
+ { endpoints: [0, constants.positive.infinity], value: constants.negative.max, expected: false },
+ { endpoints: [0, constants.positive.infinity], value: constants.negative.infinity, expected: false },
// Lower infinity
- { bounds: [constants.negative.infinity, 0], value: constants.positive.min, expected: false },
- { bounds: [constants.negative.infinity, 0], value: constants.positive.max, expected: false },
- { bounds: [constants.negative.infinity, 0], value: constants.positive.infinity, expected: false },
- { bounds: [constants.negative.infinity, 0], value: constants.negative.min, expected: true },
- { bounds: [constants.negative.infinity, 0], value: constants.negative.max, expected: true },
- { bounds: [constants.negative.infinity, 0], value: constants.negative.infinity, expected: true },
+ { endpoints: [constants.negative.infinity, 0], value: constants.positive.min, expected: false },
+ { endpoints: [constants.negative.infinity, 0], value: constants.positive.max, expected: false },
+ { endpoints: [constants.negative.infinity, 0], value: constants.positive.infinity, expected: false },
+ { endpoints: [constants.negative.infinity, 0], value: constants.negative.min, expected: true },
+ { endpoints: [constants.negative.infinity, 0], value: constants.negative.max, expected: true },
+ { endpoints: [constants.negative.infinity, 0], value: constants.negative.infinity, expected: true },
// Full infinity
- { bounds: [constants.negative.infinity, constants.positive.infinity], value: constants.positive.min, expected: true },
- { bounds: [constants.negative.infinity, constants.positive.infinity], value: constants.positive.max, expected: true },
- { bounds: [constants.negative.infinity, constants.positive.infinity], value: constants.positive.infinity, expected: true },
- { bounds: [constants.negative.infinity, constants.positive.infinity], value: constants.negative.min, expected: true },
- { bounds: [constants.negative.infinity, constants.positive.infinity], value: constants.negative.max, expected: true },
- { bounds: [constants.negative.infinity, constants.positive.infinity], value: constants.negative.infinity, expected: true },
- { bounds: [constants.negative.infinity, constants.positive.infinity], value: Number.NaN, expected: true },
+ { endpoints: [constants.negative.infinity, constants.positive.infinity], value: constants.positive.min, expected: true },
+ { endpoints: [constants.negative.infinity, constants.positive.infinity], value: constants.positive.max, expected: true },
+ { endpoints: [constants.negative.infinity, constants.positive.infinity], value: constants.positive.infinity, expected: true },
+ { endpoints: [constants.negative.infinity, constants.positive.infinity], value: constants.negative.min, expected: true },
+ { endpoints: [constants.negative.infinity, constants.positive.infinity], value: constants.negative.max, expected: true },
+ { endpoints: [constants.negative.infinity, constants.positive.infinity], value: constants.negative.infinity, expected: true },
+ { endpoints: [constants.negative.infinity, constants.positive.infinity], value: Number.NaN, expected: true },
// Maximum f32 boundary
- { bounds: [0, constants.positive.max], value: constants.positive.min, expected: true },
- { bounds: [0, constants.positive.max], value: constants.positive.max, expected: true },
- { bounds: [0, constants.positive.max], value: constants.positive.infinity, expected: false },
- { bounds: [0, constants.positive.max], value: constants.negative.min, expected: false },
- { bounds: [0, constants.positive.max], value: constants.negative.max, expected: false },
- { bounds: [0, constants.positive.max], value: constants.negative.infinity, expected: false },
+ { endpoints: [0, constants.positive.max], value: constants.positive.min, expected: true },
+ { endpoints: [0, constants.positive.max], value: constants.positive.max, expected: true },
+ { endpoints: [0, constants.positive.max], value: constants.positive.infinity, expected: false },
+ { endpoints: [0, constants.positive.max], value: constants.negative.min, expected: false },
+ { endpoints: [0, constants.positive.max], value: constants.negative.max, expected: false },
+ { endpoints: [0, constants.positive.max], value: constants.negative.infinity, expected: false },
// Minimum f32 boundary
- { bounds: [constants.negative.min, 0], value: constants.positive.min, expected: false },
- { bounds: [constants.negative.min, 0], value: constants.positive.max, expected: false },
- { bounds: [constants.negative.min, 0], value: constants.positive.infinity, expected: false },
- { bounds: [constants.negative.min, 0], value: constants.negative.min, expected: true },
- { bounds: [constants.negative.min, 0], value: constants.negative.max, expected: true },
- { bounds: [constants.negative.min, 0], value: constants.negative.infinity, expected: false },
+ { endpoints: [constants.negative.min, 0], value: constants.positive.min, expected: false },
+ { endpoints: [constants.negative.min, 0], value: constants.positive.max, expected: false },
+ { endpoints: [constants.negative.min, 0], value: constants.positive.infinity, expected: false },
+ { endpoints: [constants.negative.min, 0], value: constants.negative.min, expected: true },
+ { endpoints: [constants.negative.min, 0], value: constants.negative.max, expected: true },
+ { endpoints: [constants.negative.min, 0], value: constants.negative.infinity, expected: false },
// Subnormals
- { bounds: [0, constants.positive.min], value: constants.positive.subnormal.min, expected: true },
- { bounds: [0, constants.positive.min], value: constants.positive.subnormal.max, expected: true },
- { bounds: [0, constants.positive.min], value: constants.negative.subnormal.min, expected: false },
- { bounds: [0, constants.positive.min], value: constants.negative.subnormal.max, expected: false },
- { bounds: [constants.negative.max, 0], value: constants.positive.subnormal.min, expected: false },
- { bounds: [constants.negative.max, 0], value: constants.positive.subnormal.max, expected: false },
- { bounds: [constants.negative.max, 0], value: constants.negative.subnormal.min, expected: true },
- { bounds: [constants.negative.max, 0], value: constants.negative.subnormal.max, expected: true },
- { bounds: [0, constants.positive.subnormal.min], value: constants.positive.subnormal.min, expected: true },
- { bounds: [0, constants.positive.subnormal.min], value: constants.positive.subnormal.max, expected: false },
- { bounds: [0, constants.positive.subnormal.min], value: constants.negative.subnormal.min, expected: false },
- { bounds: [0, constants.positive.subnormal.min], value: constants.negative.subnormal.max, expected: false },
- { bounds: [constants.negative.subnormal.max, 0], value: constants.positive.subnormal.min, expected: false },
- { bounds: [constants.negative.subnormal.max, 0], value: constants.positive.subnormal.max, expected: false },
- { bounds: [constants.negative.subnormal.max, 0], value: constants.negative.subnormal.min, expected: false },
- { bounds: [constants.negative.subnormal.max, 0], value: constants.negative.subnormal.max, expected: true },
+ { endpoints: [0, constants.positive.min], value: constants.positive.subnormal.min, expected: true },
+ { endpoints: [0, constants.positive.min], value: constants.positive.subnormal.max, expected: true },
+ { endpoints: [0, constants.positive.min], value: constants.negative.subnormal.min, expected: false },
+ { endpoints: [0, constants.positive.min], value: constants.negative.subnormal.max, expected: false },
+ { endpoints: [constants.negative.max, 0], value: constants.positive.subnormal.min, expected: false },
+ { endpoints: [constants.negative.max, 0], value: constants.positive.subnormal.max, expected: false },
+ { endpoints: [constants.negative.max, 0], value: constants.negative.subnormal.min, expected: true },
+ { endpoints: [constants.negative.max, 0], value: constants.negative.subnormal.max, expected: true },
+ { endpoints: [0, constants.positive.subnormal.min], value: constants.positive.subnormal.min, expected: true },
+ { endpoints: [0, constants.positive.subnormal.min], value: constants.positive.subnormal.max, expected: false },
+ { endpoints: [0, constants.positive.subnormal.min], value: constants.negative.subnormal.min, expected: false },
+ { endpoints: [0, constants.positive.subnormal.min], value: constants.negative.subnormal.max, expected: false },
+ { endpoints: [constants.negative.subnormal.max, 0], value: constants.positive.subnormal.min, expected: false },
+ { endpoints: [constants.negative.subnormal.max, 0], value: constants.positive.subnormal.max, expected: false },
+ { endpoints: [constants.negative.subnormal.max, 0], value: constants.negative.subnormal.min, expected: false },
+ { endpoints: [constants.negative.subnormal.max, 0], value: constants.negative.subnormal.max, expected: true },
];
// Note: Out of range values are limited to infinities for abstract float, due to abstract
@@ -296,20 +296,20 @@ g.test('contains_number')
// prettier-ignore
cases.push(...[
// Out of range high
- { bounds: [0, 2 * constants.positive.max], value: constants.positive.min, expected: true },
- { bounds: [0, 2 * constants.positive.max], value: constants.positive.max, expected: true },
- { bounds: [0, 2 * constants.positive.max], value: constants.positive.infinity, expected: false },
- { bounds: [0, 2 * constants.positive.max], value: constants.negative.min, expected: false },
- { bounds: [0, 2 * constants.positive.max], value: constants.negative.max, expected: false },
- { bounds: [0, 2 * constants.positive.max], value: constants.negative.infinity, expected: false },
+ { endpoints: [0, 2 * constants.positive.max], value: constants.positive.min, expected: true },
+ { endpoints: [0, 2 * constants.positive.max], value: constants.positive.max, expected: true },
+ { endpoints: [0, 2 * constants.positive.max], value: constants.positive.infinity, expected: false },
+ { endpoints: [0, 2 * constants.positive.max], value: constants.negative.min, expected: false },
+ { endpoints: [0, 2 * constants.positive.max], value: constants.negative.max, expected: false },
+ { endpoints: [0, 2 * constants.positive.max], value: constants.negative.infinity, expected: false },
// Out of range low
- { bounds: [2 * constants.negative.min, 0], value: constants.positive.min, expected: false },
- { bounds: [2 * constants.negative.min, 0], value: constants.positive.max, expected: false },
- { bounds: [2 * constants.negative.min, 0], value: constants.positive.infinity, expected: false },
- { bounds: [2 * constants.negative.min, 0], value: constants.negative.min, expected: true },
- { bounds: [2 * constants.negative.min, 0], value: constants.negative.max, expected: true },
- { bounds: [2 * constants.negative.min, 0], value: constants.negative.infinity, expected: false },
+ { endpoints: [2 * constants.negative.min, 0], value: constants.positive.min, expected: false },
+ { endpoints: [2 * constants.negative.min, 0], value: constants.positive.max, expected: false },
+ { endpoints: [2 * constants.negative.min, 0], value: constants.positive.infinity, expected: false },
+ { endpoints: [2 * constants.negative.min, 0], value: constants.negative.min, expected: true },
+ { endpoints: [2 * constants.negative.min, 0], value: constants.negative.max, expected: true },
+ { endpoints: [2 * constants.negative.min, 0], value: constants.negative.infinity, expected: false },
] as ContainsNumberCase[]);
}
@@ -318,7 +318,7 @@ g.test('contains_number')
)
.fn(t => {
const trait = FP[t.params.trait];
- const i = trait.toInterval(t.params.bounds);
+ const i = trait.toInterval(t.params.endpoints);
const value = t.params.value;
const expected = t.params.expected;
@@ -327,8 +327,8 @@ g.test('contains_number')
});
interface ContainsIntervalCase {
- lhs: number | IntervalBounds;
- rhs: number | IntervalBounds;
+ lhs: number | IntervalEndpoints;
+ rhs: number | IntervalEndpoints;
expected: boolean;
}
@@ -440,8 +440,8 @@ g.test('contains_interval')
// Utilities
interface SpanIntervalsCase {
- intervals: (number | IntervalBounds)[];
- expected: number | IntervalBounds;
+ intervals: (number | IntervalEndpoints)[];
+ expected: number | IntervalEndpoints;
}
g.test('spanIntervals')
@@ -467,7 +467,7 @@ g.test('spanIntervals')
{ intervals: [[2, 5], [0, 1]], expected: [0, 5] },
{ intervals: [[0, 2], [1, 5]], expected: [0, 5] },
{ intervals: [[0, 5], [1, 2]], expected: [0, 5] },
- { intervals: [[constants.negative.infinity, 0], [0, constants.positive.infinity]], expected: kUnboundedBounds },
+ { intervals: [[constants.negative.infinity, 0], [0, constants.positive.infinity]], expected: kUnboundedEndpoints },
// Multiple Intervals
{ intervals: [[0, 1], [2, 3], [4, 5]], expected: [0, 5] },
@@ -494,7 +494,7 @@ g.test('spanIntervals')
});
interface isVectorCase {
- input: (number | IntervalBounds | FPIntervalParam)[];
+ input: (number | IntervalEndpoints | FPIntervalParam)[];
expected: boolean;
}
@@ -511,7 +511,7 @@ g.test('isVector')
{ input: [1, 2, 3], expected: false },
{ input: [1, 2, 3, 4], expected: false },
- // IntervalBounds
+ // IntervalEndpoints
{ input: [[1], [2]], expected: false },
{ input: [[1], [2], [3]], expected: false },
{ input: [[1], [2], [3], [4]], expected: false },
@@ -600,8 +600,8 @@ g.test('isVector')
});
interface toVectorCase {
- input: (number | IntervalBounds | FPIntervalParam)[];
- expected: (number | IntervalBounds)[];
+ input: (number | IntervalEndpoints | FPIntervalParam)[];
+ expected: (number | IntervalEndpoints)[];
}
g.test('toVector')
@@ -617,7 +617,7 @@ g.test('toVector')
{ input: [1, 2, 3], expected: [1, 2, 3] },
{ input: [1, 2, 3, 4], expected: [1, 2, 3, 4] },
- // IntervalBounds
+ // IntervalEndpoints
{ input: [[1], [2]], expected: [1, 2] },
{ input: [[1], [2], [3]], expected: [1, 2, 3] },
{ input: [[1], [2], [3], [4]], expected: [1, 2, 3, 4] },
@@ -704,7 +704,7 @@ g.test('toVector')
{ input: [1, trait.toParam([2]), [3], 4], expected: [1, 2, 3, 4] },
{
input: [1, [2], [2, 3], kUnboundedInterval[p.trait]],
- expected: [1, 2, [2, 3], kUnboundedBounds],
+ expected: [1, 2, [2, 3], kUnboundedEndpoints],
},
];
})
@@ -722,7 +722,7 @@ g.test('toVector')
});
interface isMatrixCase {
- input: (number | IntervalBounds | FPIntervalParam)[][];
+ input: (number | IntervalEndpoints | FPIntervalParam)[][];
expected: boolean;
}
@@ -808,7 +808,7 @@ g.test('isMatrix')
expected: false,
},
- // IntervalBounds
+ // IntervalEndpoints
{
input: [
[[1], [2]],
@@ -1157,8 +1157,8 @@ g.test('isMatrix')
});
interface toMatrixCase {
- input: (number | IntervalBounds | FPIntervalParam)[][];
- expected: (number | IntervalBounds)[][];
+ input: (number | IntervalEndpoints | FPIntervalParam)[][];
+ expected: (number | IntervalEndpoints)[][];
}
g.test('toMatrix')
@@ -1279,7 +1279,7 @@ g.test('toMatrix')
],
},
- // IntervalBounds
+ // IntervalEndpoints
{
input: [
[[1], [2]],
@@ -1822,7 +1822,7 @@ g.test('toMatrix')
interface AbsoluteErrorCase {
value: number;
error: number;
- expected: number | IntervalBounds;
+ expected: number | IntervalEndpoints;
}
// Special values used for testing absolute error interval
@@ -1856,23 +1856,24 @@ g.test('absoluteErrorInterval')
const smallErr = kSmallAbsoluteErrorValue[p.trait];
const largeErr = kLargeAbsoluteErrorValue[p.trait];
const subnormalErr = kSubnormalAbsoluteErrorValue[p.trait];
+
// prettier-ignore
return [
// Edge Cases
- // 1. Interval around infinity would be kUnboundedBounds
- { value: constants.positive.infinity, error: 0, expected: kUnboundedBounds },
- { value: constants.positive.infinity, error: largeErr, expected: kUnboundedBounds },
- { value: constants.positive.infinity, error: 1, expected: kUnboundedBounds },
- { value: constants.negative.infinity, error: 0, expected: kUnboundedBounds },
- { value: constants.negative.infinity, error: largeErr, expected: kUnboundedBounds },
- { value: constants.negative.infinity, error: 1, expected: kUnboundedBounds },
+ // 1. Interval around infinity would be kUnboundedEndpoints
+ { value: constants.positive.infinity, error: 0, expected: kUnboundedEndpoints },
+ { value: constants.positive.infinity, error: largeErr, expected: kUnboundedEndpoints },
+ { value: constants.positive.infinity, error: 1, expected: kUnboundedEndpoints },
+ { value: constants.negative.infinity, error: 0, expected: kUnboundedEndpoints },
+ { value: constants.negative.infinity, error: largeErr, expected: kUnboundedEndpoints },
+ { value: constants.negative.infinity, error: 1, expected: kUnboundedEndpoints },
// 2. Interval around largest finite positive/negative
{ value: constants.positive.max, error: 0, expected: constants.positive.max },
- { value: constants.positive.max, error: largeErr, expected: kUnboundedBounds},
- { value: constants.positive.max, error: constants.positive.max, expected: kUnboundedBounds},
+ { value: constants.positive.max, error: largeErr, expected: kUnboundedEndpoints},
+ { value: constants.positive.max, error: constants.positive.max, expected: kUnboundedEndpoints},
{ value: constants.negative.min, error: 0, expected: constants.negative.min },
- { value: constants.negative.min, error: largeErr, expected: kUnboundedBounds},
- { value: constants.negative.min, error: constants.positive.max, expected: kUnboundedBounds},
+ { value: constants.negative.min, error: largeErr, expected: kUnboundedEndpoints},
+ { value: constants.negative.min, error: constants.positive.max, expected: kUnboundedEndpoints},
// 3. Interval around small but normal center, center should not get flushed.
{ value: constants.positive.min, error: 0, expected: constants.positive.min },
{ value: constants.positive.min, error: smallErr, expected: [constants.positive.min - smallErr, constants.positive.min + smallErr]},
@@ -1898,6 +1899,19 @@ g.test('absoluteErrorInterval')
{ value: constants.negative.subnormal.max, error: smallErr, expected: [constants.negative.subnormal.max - smallErr, smallErr] },
{ value: constants.negative.subnormal.max, error: 1, expected: [constants.negative.subnormal.max - 1, 1] },
+ // Zero
+ { value: 0, error: 0, expected: 0 },
+ { value: 0, error: smallErr, expected: [-smallErr, smallErr] },
+ { value: 0, error: 1, expected: [-1, 1] },
+
+ // Two
+ { value: 2, error: 0, expected: 2 },
+ { value: 2, error: smallErr, expected: [2 - smallErr, 2 + smallErr] },
+ { value: 2, error: 1, expected: [1, 3] },
+ { value: -2, error: 0, expected: -2 },
+ { value: -2, error: smallErr, expected: [-2 - smallErr, -2 + smallErr] },
+ { value: -2, error: 1, expected: [-3, -1] },
+
// 64-bit subnormals, expected to be treated as 0.0 or smallest subnormal of kind.
{ value: reinterpretU64AsF64(0x0000_0000_0000_0001n), error: 0, expected: [0, constants.positive.subnormal.min] },
{ value: reinterpretU64AsF64(0x0000_0000_0000_0001n), error: subnormalErr, expected: [-subnormalErr, constants.positive.subnormal.min + subnormalErr] },
@@ -1912,19 +1926,6 @@ g.test('absoluteErrorInterval')
{ value: reinterpretU64AsF64(0x800f_ffff_ffff_fffen), error: 0, expected: [constants.negative.subnormal.max, 0] },
{ value: reinterpretU64AsF64(0x800f_ffff_ffff_fffen), error: subnormalErr, expected: [constants.negative.subnormal.max - subnormalErr, subnormalErr] },
{ value: reinterpretU64AsF64(0x800f_ffff_ffff_fffen), error: 1, expected: [constants.negative.subnormal.max - 1, 1] },
-
- // Zero
- { value: 0, error: 0, expected: 0 },
- { value: 0, error: smallErr, expected: [-smallErr, smallErr] },
- { value: 0, error: 1, expected: [-1, 1] },
-
- // Two
- { value: 2, error: 0, expected: 2 },
- { value: 2, error: smallErr, expected: [2 - smallErr, 2 + smallErr] },
- { value: 2, error: 1, expected: [1, 3] },
- { value: -2, error: 0, expected: -2 },
- { value: -2, error: smallErr, expected: [-2 - smallErr, -2 + smallErr] },
- { value: -2, error: 1, expected: [-3, -1] },
];
})
)
@@ -1942,7 +1943,7 @@ g.test('absoluteErrorInterval')
interface CorrectlyRoundedCase {
value: number;
- expected: number | IntervalBounds;
+ expected: number | IntervalEndpoints;
}
// Correctly rounded cases that input values are exactly representable normal values of target type
@@ -2034,8 +2035,8 @@ g.test('correctlyRoundedInterval')
// prettier-ignore
return [
// Edge Cases
- { value: constants.positive.infinity, expected: kUnboundedBounds },
- { value: constants.negative.infinity, expected: kUnboundedBounds },
+ { value: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { value: constants.negative.infinity, expected: kUnboundedEndpoints },
{ value: constants.positive.max, expected: constants.positive.max },
{ value: constants.negative.min, expected: constants.negative.min },
{ value: constants.positive.min, expected: constants.positive.min },
@@ -2074,7 +2075,7 @@ g.test('correctlyRoundedInterval')
interface ULPCase {
value: number;
num_ulp: number;
- expected: number | IntervalBounds;
+ expected: number | IntervalEndpoints;
}
// Special values used for testing ULP error interval
@@ -2086,7 +2087,7 @@ const kULPErrorValue = {
g.test('ulpInterval')
.params(u =>
u
- .combine('trait', ['abstract', 'f32', 'f16'] as const)
+ .combine('trait', ['f32', 'f16'] as const)
.beginSubcases()
.expandWithParams<ULPCase>(p => {
const trait = kFPTraitForULP[p.trait];
@@ -2099,21 +2100,21 @@ g.test('ulpInterval')
// prettier-ignore
return [
// Edge Cases
- { value: constants.positive.infinity, num_ulp: 0, expected: kUnboundedBounds },
- { value: constants.positive.infinity, num_ulp: 1, expected: kUnboundedBounds },
- { value: constants.positive.infinity, num_ulp: ULPValue, expected: kUnboundedBounds },
- { value: constants.negative.infinity, num_ulp: 0, expected: kUnboundedBounds },
- { value: constants.negative.infinity, num_ulp: 1, expected: kUnboundedBounds },
- { value: constants.negative.infinity, num_ulp: ULPValue, expected: kUnboundedBounds },
+ { value: constants.positive.infinity, num_ulp: 0, expected: kUnboundedEndpoints },
+ { value: constants.positive.infinity, num_ulp: 1, expected: kUnboundedEndpoints },
+ { value: constants.positive.infinity, num_ulp: ULPValue, expected: kUnboundedEndpoints },
+ { value: constants.negative.infinity, num_ulp: 0, expected: kUnboundedEndpoints },
+ { value: constants.negative.infinity, num_ulp: 1, expected: kUnboundedEndpoints },
+ { value: constants.negative.infinity, num_ulp: ULPValue, expected: kUnboundedEndpoints },
{ value: constants.positive.max, num_ulp: 0, expected: constants.positive.max },
- { value: constants.positive.max, num_ulp: 1, expected: kUnboundedBounds },
- { value: constants.positive.max, num_ulp: ULPValue, expected: kUnboundedBounds },
+ { value: constants.positive.max, num_ulp: 1, expected: kUnboundedEndpoints },
+ { value: constants.positive.max, num_ulp: ULPValue, expected: kUnboundedEndpoints },
{ value: constants.positive.min, num_ulp: 0, expected: constants.positive.min },
{ value: constants.positive.min, num_ulp: 1, expected: [0, plusOneULP(constants.positive.min)] },
{ value: constants.positive.min, num_ulp: ULPValue, expected: [0, plusNULP(constants.positive.min, ULPValue)] },
{ value: constants.negative.min, num_ulp: 0, expected: constants.negative.min },
- { value: constants.negative.min, num_ulp: 1, expected: kUnboundedBounds },
- { value: constants.negative.min, num_ulp: ULPValue, expected: kUnboundedBounds },
+ { value: constants.negative.min, num_ulp: 1, expected: kUnboundedEndpoints },
+ { value: constants.negative.min, num_ulp: ULPValue, expected: kUnboundedEndpoints },
{ value: constants.negative.max, num_ulp: 0, expected: constants.negative.max },
{ value: constants.negative.max, num_ulp: 1, expected: [minusOneULP(constants.negative.max), 0] },
{ value: constants.negative.max, num_ulp: ULPValue, expected: [minusNULP(constants.negative.max, ULPValue), 0] },
@@ -2178,7 +2179,7 @@ const kConstantCorrectlyRoundedExpectation = {
'1.9': [reinterpretU32AsF32(0x3ff33333), reinterpretU32AsF32(0x3ff33334)],
// -1.9 falls between f32 0xBFF33334 and 0xBFF33333
'-1.9': [reinterpretU32AsF32(0xbff33334), reinterpretU32AsF32(0xbff33333)],
- } as { [value in ConstantNumberFrequentlyUsedInCases]: IntervalBounds },
+ } as { [value in ConstantNumberFrequentlyUsedInCases]: IntervalEndpoints },
f16: {
// 0.1 falls between f16 0x2E66 and 0x2E67
'0.1': [reinterpretU16AsF16(0x2e66), reinterpretU16AsF16(0x2e67)],
@@ -2188,7 +2189,7 @@ const kConstantCorrectlyRoundedExpectation = {
'1.9': [reinterpretU16AsF16(0x3f99), reinterpretU16AsF16(0x3f9a)],
// 1.9 falls between f16 0xBF9A and 0xBF99
'-1.9': [reinterpretU16AsF16(0xbf9a), reinterpretU16AsF16(0xbf99)],
- } as { [value in ConstantNumberFrequentlyUsedInCases]: IntervalBounds },
+ } as { [value in ConstantNumberFrequentlyUsedInCases]: IntervalEndpoints },
// Since abstract is actually f64 and JS number is also f64, the JS number value will map to
// identical abstracty value without rounded.
abstract: {
@@ -2201,7 +2202,7 @@ const kConstantCorrectlyRoundedExpectation = {
interface ScalarToIntervalCase {
input: number;
- expected: number | IntervalBounds;
+ expected: number | IntervalEndpoints;
}
g.test('absInterval')
@@ -2224,8 +2225,8 @@ g.test('absInterval')
{ input: -1.9, expected: kConstantCorrectlyRoundedExpectation[p.trait]['1.9']},
// Edge cases
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ input: constants.positive.max, expected: constants.positive.max },
{ input: constants.positive.min, expected: constants.positive.min },
{ input: constants.negative.min, expected: constants.positive.max },
@@ -2290,17 +2291,18 @@ g.test('acosInterval')
const constants = trait.constants();
// prettier-ignore
return [
- // The acceptance interval @ x = -1 and 1 is kUnboundedBounds, because
- // sqrt(1 - x*x) = sqrt(0), and sqrt is defined in terms of inverseqrt
- // The acceptance interval @ x = 0 is kUnboundedBounds, because atan2 is not
- // well-defined/implemented at 0.
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: -1, expected: kUnboundedBounds },
- { input: 0, expected: kUnboundedBounds },
- { input: 1, expected: kUnboundedBounds },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ // The acceptance interval @ x = -1 and 1 is kUnboundedEndpoints,
+ // because sqrt(1 - x*x) = sqrt(0), and sqrt is defined in terms of
+ // inverseqrt.
+ // The acceptance interval @ x = 0 is kUnboundedEndpoints, because atan2
+ // is not well-defined/implemented at 0.
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: -1, expected: kUnboundedEndpoints },
+ { input: 0, expected: kUnboundedEndpoints },
+ { input: 1, expected: kUnboundedEndpoints },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
// Cases that bounded by absolute error and inherited from atan2(sqrt(1-x*x), x). Note that
// even x is very close to 1.0 and the expected result is close to 0.0, the expected
@@ -2346,13 +2348,13 @@ g.test('acoshAlternativeInterval')
return [
...kAcoshAlternativeIntervalCases[p.trait],
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: -1, expected: kUnboundedBounds },
- { input: 0, expected: kUnboundedBounds },
- { input: 1, expected: kUnboundedBounds }, // 1/0 occurs in inverseSqrt in this formulation
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: -1, expected: kUnboundedEndpoints },
+ { input: 0, expected: kUnboundedEndpoints },
+ { input: 1, expected: kUnboundedEndpoints }, // 1/0 occurs in inverseSqrt in this formulation
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -2392,13 +2394,13 @@ g.test('acoshPrimaryInterval')
return [
...kAcoshPrimaryIntervalCases[p.trait],
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: -1, expected: kUnboundedBounds },
- { input: 0, expected: kUnboundedBounds },
- { input: 1, expected: kUnboundedBounds }, // 1/0 occurs in inverseSqrt in this formulation
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: -1, expected: kUnboundedEndpoints },
+ { input: 0, expected: kUnboundedEndpoints },
+ { input: 1, expected: kUnboundedEndpoints }, // 1/0 occurs in inverseSqrt in this formulation
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -2434,28 +2436,29 @@ g.test('asinInterval')
.expandWithParams<ScalarToIntervalCase>(p => {
const trait = FP[p.trait];
const constants = trait.constants();
- const abs_error = p.trait === 'f32' ? 6.77e-5 : 3.91e-3;
+ const abs_error = p.trait === 'f32' ? 6.81e-5 : 3.91e-3;
// prettier-ignore
return [
- // The acceptance interval @ x = -1 and 1 is kUnboundedBounds, because
- // sqrt(1 - x*x) = sqrt(0), and sqrt is defined in terms of inversqrt.
- // The acceptance interval @ x = 0 is kUnboundedBounds, because atan2 is not
- // well-defined/implemented at 0.
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: -1, expected: kUnboundedBounds },
- // Subnormal input may get flushed to 0, and result in kUnboundedBounds.
- { input: constants.negative.subnormal.min, expected: kUnboundedBounds },
- { input: 0, expected: kUnboundedBounds },
- { input: constants.positive.subnormal.max, expected: kUnboundedBounds },
- { input: 1, expected: kUnboundedBounds },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ // The acceptance interval @ x = -1 and 1 is kUnboundedEndpoints,
+ // because sqrt(1 - x*x) = sqrt(0), and sqrt is defined in terms of
+ // inversqrt.
+ // The acceptance interval @ x = 0 is kUnboundedEndpoints, because
+ // atan2 is not well-defined/implemented at 0.
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: -1, expected: kUnboundedEndpoints },
+ // Subnormal input may get flushed to 0, and result in kUnboundedEndpoints.
+ { input: constants.negative.subnormal.min, expected: kUnboundedEndpoints },
+ { input: 0, expected: kUnboundedEndpoints },
+ { input: constants.positive.subnormal.max, expected: kUnboundedEndpoints },
+ { input: 1, expected: kUnboundedEndpoints },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
// When input near 0, the expected result is bounded by absolute error rather than ULP
// error. Away from 0 the atan2 inherited error should be larger.
- { input: constants.negative.max, expected: trait.absoluteErrorInterval(Math.asin(constants.negative.max), abs_error).bounds() }, // ~0
- { input: constants.positive.min, expected: trait.absoluteErrorInterval(Math.asin(constants.positive.min), abs_error).bounds() }, // ~0
+ { input: constants.negative.max, expected: trait.absoluteErrorInterval(Math.asin(constants.negative.max), abs_error).endpoints() }, // ~0
+ { input: constants.positive.min, expected: trait.absoluteErrorInterval(Math.asin(constants.positive.min), abs_error).endpoints() }, // ~0
// Cases that inherited from atan2(x, sqrt(1-x*x))
...kAsinIntervalInheritedCases[p.trait],
@@ -2500,10 +2503,10 @@ g.test('asinhInterval')
return [
...kAsinhIntervalCases[p.trait],
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -2569,8 +2572,8 @@ g.test('atanInterval')
{ input: 0, expected: 0 },
...kAtanIntervalCases[p.trait],
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -2619,12 +2622,12 @@ g.test('atanhInterval')
return [
...kAtanhIntervalCases[p.trait],
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: -1, expected: kUnboundedBounds },
- { input: 1, expected: kUnboundedBounds },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: -1, expected: kUnboundedEndpoints },
+ { input: 1, expected: kUnboundedEndpoints },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -2650,12 +2653,17 @@ const kCeilIntervalCases = {
{ input: -(2 ** 14), expected: -(2 ** 14) },
{ input: 0x8000, expected: 0x8000 }, // https://github.com/gpuweb/cts/issues/2766
],
+ abstract: [
+ { input: 2 ** 52, expected: 2 ** 52 },
+ { input: -(2 ** 52), expected: -(2 ** 52) },
+ { input: 0x8000000000000000, expected: 0x8000000000000000 }, // https://github.com/gpuweb/cts/issues/2766
+ ],
} as const;
g.test('ceilInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16'] as const)
+ .combine('trait', ['f32', 'f16', 'abstract'] as const)
.beginSubcases()
.expandWithParams<ScalarToIntervalCase>(p => {
const constants = FP[p.trait].constants();
@@ -2674,15 +2682,15 @@ g.test('ceilInterval')
{ input: -1.9, expected: -1 },
// Edge cases
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ 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
+ // Subnormals
{ input: constants.positive.subnormal.max, expected: [0, 1] },
{ input: constants.positive.subnormal.min, expected: [0, 1] },
{ input: constants.negative.subnormal.min, expected: 0 },
@@ -2714,12 +2722,12 @@ const kCosIntervalThirdPiCases = {
// cos(-1.046875) = 0.50027931
{
input: kValue.f16.negative.pi.third,
- expected: FP['f16'].correctlyRoundedInterval(0.50027931).bounds(),
+ expected: FP['f16'].correctlyRoundedInterval(0.50027931).endpoints(),
},
// cos(1.046875) = 0.50027931
{
input: kValue.f16.positive.pi.third,
- expected: FP['f16'].correctlyRoundedInterval(0.50027931).bounds(),
+ expected: FP['f16'].correctlyRoundedInterval(0.50027931).endpoints(),
},
],
};
@@ -2740,13 +2748,13 @@ g.test('cosInterval')
// substantially different, so instead of getting 0 you get a value on the
// order of 10^-8 away from 0, thus difficult to express in a
// human-readable manner.
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
{ input: constants.negative.pi.whole, expected: [-1, kPlusOneULPFunctions[p.trait](-1)] },
{ input: 0, expected: [1, 1] },
{ input: constants.positive.pi.whole, expected: [-1, kPlusOneULPFunctions[p.trait](-1)] },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
...(kCosIntervalThirdPiCases[p.trait] as ScalarToIntervalCase[]),
];
@@ -2796,10 +2804,10 @@ g.test('coshInterval')
return [
...kCoshIntervalCases[p.trait],
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -2843,37 +2851,23 @@ const kDegreesIntervalCases = {
{ input: kValue.f16.positive.pi.three_quarters, expected: [kMinusOneULPFunctions['f16'](135), 135] },
{ input: kValue.f16.positive.pi.whole, expected: [kMinusOneULPFunctions['f16'](180), 180] },
] as ScalarToIntervalCase[],
- abstract: [
- { input: kValue.f64.negative.pi.whole, expected: -180 },
- { input: kValue.f64.negative.pi.three_quarters, expected: -135 },
- { input: kValue.f64.negative.pi.half, expected: -90 },
- { input: kValue.f64.negative.pi.third, expected: kPlusOneULPFunctions['abstract'](-60) },
- { input: kValue.f64.negative.pi.quarter, expected: -45 },
- { input: kValue.f64.negative.pi.sixth, expected: kPlusOneULPFunctions['abstract'](-30) },
- { input: kValue.f64.positive.pi.sixth, expected: kMinusOneULPFunctions['abstract'](30) },
- { input: kValue.f64.positive.pi.quarter, expected: 45 },
- { input: kValue.f64.positive.pi.third, expected: kMinusOneULPFunctions['abstract'](60) },
- { input: kValue.f64.positive.pi.half, expected: 90 },
- { input: kValue.f64.positive.pi.three_quarters, expected: 135 },
- { input: kValue.f64.positive.pi.whole, expected: 180 },
- ] as ScalarToIntervalCase[],
} as const;
g.test('degreesInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16', 'abstract'] as const)
+ .combine('trait', ['f32', 'f16'] as const)
.beginSubcases()
.expandWithParams<ScalarToIntervalCase>(p => {
const trait = p.trait;
const constants = FP[trait].constants();
// prettier-ignore
return [
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
{ input: 0, expected: 0 },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
...kDegreesIntervalCases[trait]
];
})
@@ -2895,14 +2889,14 @@ const kExpIntervalCases = {
// exp(88) = 1.6516362549940018555283297962649e+38 = 0x7ef882b6/7.
{ input: 88, expected: [reinterpretU32AsF32(0x7ef882b6), reinterpretU32AsF32(0x7ef882b7)] },
// exp(89) overflow f32.
- { input: 89, expected: kUnboundedBounds },
+ { input: 89, expected: kUnboundedEndpoints },
] as ScalarToIntervalCase[],
f16: [
{ input: 1, expected: [kValue.f16.positive.e, kPlusOneULPFunctions['f16'](kValue.f16.positive.e)] },
// exp(11) = 59874.141715197818455326485792258 = 0x7b4f/0x7b50.
{ input: 11, expected: [reinterpretU16AsF16(0x7b4f), reinterpretU16AsF16(0x7b50)] },
// exp(12) = 162754.79141900392080800520489849 overflow f16.
- { input: 12, expected: kUnboundedBounds },
+ { input: 12, expected: kUnboundedEndpoints },
] as ScalarToIntervalCase[],
} as const;
@@ -2916,7 +2910,7 @@ g.test('expInterval')
const constants = FP[trait].constants();
// prettier-ignore
return [
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ input: 0, expected: 1 },
...kExpIntervalCases[trait],
];
@@ -2954,13 +2948,13 @@ const kExp2IntervalCases = {
// exp2(127) = 1.7014118346046923173168730371588e+38 = 0x7f000000, 3 + 2 * 127 = 258 ulps.
{ input: 127, expected: reinterpretU32AsF32(0x7f000000) },
// exp2(128) overflow f32.
- { input: 128, expected: kUnboundedBounds },
+ { input: 128, expected: kUnboundedEndpoints },
] as ScalarToIntervalCase[],
f16: [
// exp2(15) = 32768 = 0x7800, 1 + 2 * 15 = 31 ulps
{ input: 15, expected: reinterpretU16AsF16(0x7800) },
// exp2(16) = 65536 overflow f16.
- { input: 16, expected: kUnboundedBounds },
+ { input: 16, expected: kUnboundedEndpoints },
] as ScalarToIntervalCase[],
} as const;
@@ -2974,7 +2968,7 @@ g.test('exp2Interval')
const constants = FP[trait].constants();
// prettier-ignore
return [
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ input: 0, expected: 1 },
{ input: 1, expected: 2 },
...kExp2IntervalCases[trait],
@@ -3051,8 +3045,8 @@ g.test('floorInterval')
{ input: -1.9, expected: -2 },
// Edge cases
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ input: constants.positive.max, expected: constants.positive.max },
{ input: constants.positive.min, expected: 0 },
{ input: constants.negative.min, expected: constants.negative.min },
@@ -3099,12 +3093,24 @@ const kFractIntervalCases = {
{ input: -1.1, expected: [reinterpretU16AsF16(0x3b32), reinterpretU16AsF16(0x3b34)] }, // ~0.9
{ input: 658.5, expected: 0.5 },
] as ScalarToIntervalCase[],
+ abstract: [
+ { input: 0.1, expected: reinterpretU64AsF64(0x3fb999999999999an) },
+ { input: 0.9, expected: reinterpretU64AsF64(0x3feccccccccccccdn) },
+ { input: 1.1, expected: reinterpretU64AsF64(0x3fb99999999999a0n) },
+ { input: -0.1, expected: reinterpretU64AsF64(0x3feccccccccccccdn) },
+ { input: -0.9, expected: reinterpretU64AsF64(0x3fb9999999999998n) },
+ { input: -1.1, expected: reinterpretU64AsF64(0x3fecccccccccccccn) },
+
+ // https://github.com/gpuweb/cts/issues/2766
+ { input: 0x80000000, expected: 0 },
+ ] as ScalarToIntervalCase[],
+
} as const;
g.test('fractInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16'] as const)
+ .combine('trait', ['f32', 'f16', 'abstract'] as const)
.beginSubcases()
.expandWithParams<ScalarToIntervalCase>(p => {
const constants = FP[p.trait].constants();
@@ -3117,8 +3123,8 @@ g.test('fractInterval')
...kFractIntervalCases[p.trait],
// Edge cases
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ input: constants.positive.max, expected: 0 },
{ input: constants.positive.min, expected: constants.positive.min },
{ input: constants.negative.min, expected: 0 },
@@ -3180,9 +3186,9 @@ g.test('inverseSqrtInterval')
{ input: 100, expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1'] }, // ~0.1
// Out of definition domain
- { input: -1, expected: kUnboundedBounds },
- { input: 0, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: -1, expected: kUnboundedEndpoints },
+ { input: 0, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -3215,7 +3221,7 @@ const kRootSumSquareExpectionInterval = {
'[1.0, 1.0]' : [reinterpretU64AsF64(0x3ff6_a09d_b000_0000n), reinterpretU64AsF64(0x3ff6_a09f_1000_0000n)], // ~√2
'[1.0, 1.0, 1.0]' : [reinterpretU64AsF64(0x3ffb_b67a_1000_0000n), reinterpretU64AsF64(0x3ffb_b67b_b000_0000n)], // ~√3
'[1.0, 1.0, 1.0, 1.0]' : [reinterpretU64AsF64(0x3fff_ffff_7000_0000n), reinterpretU64AsF64(0x4000_0000_9000_0000n)], // ~2
- } as {[s: string]: IntervalBounds},
+ } as {[s: string]: IntervalEndpoints},
f16: {
'[0.1]': [reinterpretU64AsF64(0x3fb9_7e00_0000_0000n), reinterpretU64AsF64(0x3fb9_b600_0000_0000n)], // ~0.1
'[1.0]' : [reinterpretU64AsF64(0x3fef_ee00_0000_0000n), reinterpretU64AsF64(0x3ff0_1200_0000_0000n)], // ~1.0
@@ -3223,7 +3229,7 @@ const kRootSumSquareExpectionInterval = {
'[1.0, 1.0]' : [reinterpretU64AsF64(0x3ff6_8a00_0000_0000n), reinterpretU64AsF64(0x3ff6_b600_0000_0000n)], // ~√2
'[1.0, 1.0, 1.0]' : [reinterpretU64AsF64(0x3ffb_9a00_0000_0000n), reinterpretU64AsF64(0x3ffb_d200_0000_0000n)], // ~√3
'[1.0, 1.0, 1.0, 1.0]' : [reinterpretU64AsF64(0x3fff_ee00_0000_0000n), reinterpretU64AsF64(0x4000_1200_0000_0000n)], // ~2
- } as {[s: string]: IntervalBounds},
+ } as {[s: string]: IntervalEndpoints},
} as const;
g.test('lengthIntervalScalar')
@@ -3243,22 +3249,22 @@ g.test('lengthIntervalScalar')
{input: 10.0, expected: kRootSumSquareExpectionInterval[p.trait]['[10]'] }, // ~10
{input: -10.0, expected: kRootSumSquareExpectionInterval[p.trait]['[10]'] }, // ~10
- // length(0) = kUnboundedBounds, because length uses sqrt, which is defined as 1/inversesqrt
- {input: 0, expected: kUnboundedBounds },
+ // length(0) = kUnboundedEndpoints, because length uses sqrt, which is defined as 1/inversesqrt
+ {input: 0, expected: kUnboundedEndpoints },
// Subnormal Cases
- { input: constants.negative.subnormal.min, expected: kUnboundedBounds },
- { input: constants.negative.subnormal.max, expected: kUnboundedBounds },
- { input: constants.positive.subnormal.min, expected: kUnboundedBounds },
- { input: constants.positive.subnormal.max, expected: kUnboundedBounds },
+ { input: constants.negative.subnormal.min, expected: kUnboundedEndpoints },
+ { input: constants.negative.subnormal.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.subnormal.min, expected: kUnboundedEndpoints },
+ { input: constants.positive.subnormal.max, expected: kUnboundedEndpoints },
// Edge cases
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: constants.negative.max, expected: kUnboundedBounds },
- { input: constants.positive.min, expected: kUnboundedBounds },
- { input: constants.positive.max, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: constants.negative.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.min, expected: kUnboundedEndpoints },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
];
})
)
@@ -3300,8 +3306,8 @@ g.test('logInterval')
.expandWithParams<ScalarToIntervalCase>(p => {
// prettier-ignore
return [
- { input: -1, expected: kUnboundedBounds },
- { input: 0, expected: kUnboundedBounds },
+ { input: -1, expected: kUnboundedEndpoints },
+ { input: 0, expected: kUnboundedEndpoints },
{ input: 1, expected: 0 },
...kLogIntervalCases[p.trait],
];
@@ -3348,8 +3354,8 @@ g.test('log2Interval')
.expandWithParams<ScalarToIntervalCase>(p => {
// prettier-ignore
return [
- { input: -1, expected: kUnboundedBounds },
- { input: 0, expected: kUnboundedBounds },
+ { input: -1, expected: kUnboundedEndpoints },
+ { input: 0, expected: kUnboundedEndpoints },
{ input: 1, expected: 0 },
{ input: 2, expected: 1 },
{ input: 16, expected: 4 },
@@ -3387,8 +3393,8 @@ g.test('negationInterval')
// prettier-ignore
return [
// Edge cases
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ input: constants.positive.max, expected: constants.negative.min },
{ input: constants.positive.min, expected: constants.negative.max },
{ input: constants.negative.min, expected: constants.positive.max },
@@ -3425,8 +3431,8 @@ g.test('quantizeToF16Interval')
.paramsSubcasesOnly<ScalarToIntervalCase>(
// prettier-ignore
[
- { input: kValue.f32.negative.infinity, expected: kUnboundedBounds },
- { input: kValue.f32.negative.min, expected: kUnboundedBounds },
+ { input: kValue.f32.negative.infinity, expected: kUnboundedEndpoints },
+ { input: kValue.f32.negative.min, expected: kUnboundedEndpoints },
{ input: kValue.f16.negative.min, expected: kValue.f16.negative.min },
{ input: -1.9, expected: kConstantCorrectlyRoundedExpectation['f16']['-1.9'] }, // ~-1.9
{ input: -1, expected: -1 },
@@ -3444,8 +3450,8 @@ g.test('quantizeToF16Interval')
{ input: 1, expected: 1 },
{ input: 1.9, expected: kConstantCorrectlyRoundedExpectation['f16']['1.9'] }, // ~1.9
{ input: kValue.f16.positive.max, expected: kValue.f16.positive.max },
- { input: kValue.f32.positive.max, expected: kUnboundedBounds },
- { input: kValue.f32.positive.infinity, expected: kUnboundedBounds },
+ { input: kValue.f32.positive.max, expected: kUnboundedEndpoints },
+ { input: kValue.f32.positive.infinity, expected: kUnboundedEndpoints },
]
)
.fn(t => {
@@ -3488,35 +3494,21 @@ const kRadiansIntervalCases = {
{ input: 135, expected: [kMinusOneULPFunctions['f16'](kValue.f16.positive.pi.three_quarters), kPlusOneULPFunctions['f16'](kValue.f16.positive.pi.three_quarters)] },
{ input: 180, expected: [kMinusOneULPFunctions['f16'](kValue.f16.positive.pi.whole), kPlusOneULPFunctions['f16'](kValue.f16.positive.pi.whole)] },
] as ScalarToIntervalCase[],
- abstract: [
- { input: -180, expected: kValue.f64.negative.pi.whole },
- { input: -135, expected: kValue.f64.negative.pi.three_quarters },
- { input: -90, expected: kValue.f64.negative.pi.half },
- { input: -60, expected: kValue.f64.negative.pi.third },
- { input: -45, expected: kValue.f64.negative.pi.quarter },
- { input: -30, expected: kValue.f64.negative.pi.sixth },
- { input: 30, expected: kValue.f64.positive.pi.sixth },
- { input: 45, expected: kValue.f64.positive.pi.quarter },
- { input: 60, expected: kValue.f64.positive.pi.third },
- { input: 90, expected: kValue.f64.positive.pi.half },
- { input: 135, expected: kValue.f64.positive.pi.three_quarters },
- { input: 180, expected: kValue.f64.positive.pi.whole },
- ] as ScalarToIntervalCase[],
} as const;
g.test('radiansInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16', 'abstract'] as const)
+ .combine('trait', ['f32', 'f16'] as const)
.beginSubcases()
.expandWithParams<ScalarToIntervalCase>(p => {
const trait = p.trait;
const constants = FP[trait].constants();
// prettier-ignore
return [
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
{ input: 0, expected: 0 },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
...kRadiansIntervalCases[trait]
];
})
@@ -3536,19 +3528,27 @@ const kRoundIntervalCases = {
f32: [
{ input: 2 ** 30, expected: 2 ** 30 },
{ input: -(2 ** 30), expected: -(2 ** 30) },
- { input: 0x80000000, expected: 0x80000000 }, // https://github.com/gpuweb/cts/issues/2766
+ { input: 0x8000_0000, expected: 0x8000_0000 }, // https://github.com/gpuweb/cts/issues/2766
],
f16: [
{ input: 2 ** 14, expected: 2 ** 14 },
{ input: -(2 ** 14), expected: -(2 ** 14) },
{ input: 0x8000, expected: 0x8000 }, // https://github.com/gpuweb/cts/issues/2766
],
+ abstract: [
+ { input: 2 ** 62, expected: 2 ** 62 },
+ { input: -(2 ** 62), expected: -(2 ** 62) },
+ {
+ input: 0x8000_0000_0000_0000,
+ expected: 0x8000_0000_0000_0000,
+ }, // https://github.com/gpuweb/cts/issues/2766
+ ],
} as const;
g.test('roundInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16'] as const)
+ .combine('trait', ['f32', 'f16', 'abstract'] as const)
.beginSubcases()
.expandWithParams<ScalarToIntervalCase>(p => {
const constants = FP[p.trait].constants();
@@ -3571,15 +3571,15 @@ g.test('roundInterval')
{ input: -1.9, expected: -2 },
// Edge cases
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ 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
+ // Subnormals
{ input: constants.positive.subnormal.max, expected: 0 },
{ input: constants.positive.subnormal.min, expected: 0 },
{ input: constants.negative.subnormal.min, expected: 0 },
@@ -3627,8 +3627,8 @@ g.test('saturateInterval')
{ input: constants.negative.subnormal.max, expected: [constants.negative.subnormal.max, 0.0] },
// Infinities
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -3651,7 +3651,7 @@ g.test('signInterval')
const constants = FP[p.trait].constants();
// prettier-ignore
return [
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ input: constants.negative.min, expected: -1 },
{ input: -10, expected: -1 },
{ input: -1, expected: -1 },
@@ -3667,7 +3667,7 @@ g.test('signInterval')
{ input: 1, expected: 1 },
{ input: 10, expected: 1 },
{ input: constants.positive.max, expected: 1 },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -3696,13 +3696,13 @@ g.test('sinInterval')
// substantially different, so instead of getting 0 you get a value on the
// order of 10^-8 away from it, thus difficult to express in a
// human-readable manner.
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
{ input: constants.negative.pi.half, expected: [-1, kPlusOneULPFunctions[p.trait](-1)] },
{ input: 0, expected: 0 },
{ input: constants.positive.pi.half, expected: [kMinusOneULPFunctions[p.trait](1), 1] },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -3750,10 +3750,10 @@ g.test('sinhInterval')
return [
...kSinhIntervalCases[p.trait],
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -3832,9 +3832,9 @@ g.test('sqrtInterval')
...kSqrtIntervalCases[p.trait],
// Cases out of definition domain
- { input: -1, expected: kUnboundedBounds },
- { input: 0, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: -1, expected: kUnboundedEndpoints },
+ { input: 0, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -3913,12 +3913,12 @@ g.test('tanInterval')
...kTanIntervalCases[p.trait],
// Cases that result in unbounded interval.
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: constants.negative.pi.half, expected: kUnboundedBounds },
- { input: constants.positive.pi.half, expected: kUnboundedBounds },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: constants.negative.pi.half, expected: kUnboundedEndpoints },
+ { input: constants.positive.pi.half, expected: kUnboundedEndpoints },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -3960,10 +3960,10 @@ g.test('tanhInterval')
return [
...kTanhIntervalCases[p.trait],
- { input: constants.negative.infinity, expected: kUnboundedBounds },
- { input: constants.negative.min, expected: kUnboundedBounds },
- { input: constants.positive.max, expected: kUnboundedBounds },
- { input: constants.positive.infinity, expected: kUnboundedBounds },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.min, expected: kUnboundedEndpoints },
+ { input: constants.positive.max, expected: kUnboundedEndpoints },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
];
})
)
@@ -4007,8 +4007,8 @@ g.test('truncInterval')
{ input: constants.negative.subnormal.max, expected: 0 },
// Edge cases
- { input: constants.positive.infinity, expected: kUnboundedBounds },
- { input: constants.negative.infinity, expected: kUnboundedBounds },
+ { input: constants.positive.infinity, expected: kUnboundedEndpoints },
+ { input: constants.negative.infinity, expected: kUnboundedEndpoints },
{ input: constants.positive.max, expected: constants.positive.max },
{ input: constants.positive.min, expected: 0 },
{ input: constants.negative.min, expected: constants.negative.min },
@@ -4030,7 +4030,7 @@ interface ScalarPairToIntervalCase {
// input is a pair of independent values, not a range, so should not be
// converted to a FPInterval.
input: [number, number];
- expected: number | IntervalBounds;
+ expected: number | IntervalEndpoints;
}
// prettier-ignore
@@ -4112,14 +4112,14 @@ g.test('additionInterval')
{ input: [0, constants.negative.subnormal.min], expected: [constants.negative.subnormal.min, 0] },
// 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 },
+ { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
];
})
)
@@ -4226,33 +4226,33 @@ g.test('atan2Interval')
// Cases that y out of bound.
// positive y, positive x
- { input: [Number.POSITIVE_INFINITY, 1], expected: kUnboundedBounds },
+ { input: [Number.POSITIVE_INFINITY, 1], expected: kUnboundedEndpoints },
// positive y, negative x
- { input: [Number.POSITIVE_INFINITY, -1], expected: kUnboundedBounds },
+ { input: [Number.POSITIVE_INFINITY, -1], expected: kUnboundedEndpoints },
// negative y, negative x
- { input: [Number.NEGATIVE_INFINITY, -1], expected: kUnboundedBounds },
+ { input: [Number.NEGATIVE_INFINITY, -1], expected: kUnboundedEndpoints },
// negative y, positive x
- { input: [Number.NEGATIVE_INFINITY, 1], expected: kUnboundedBounds },
+ { input: [Number.NEGATIVE_INFINITY, 1], expected: kUnboundedEndpoints },
// Discontinuity @ origin (0,0)
- { input: [0, 0], expected: kUnboundedBounds },
- { input: [0, constants.positive.subnormal.max], expected: kUnboundedBounds },
- { input: [0, constants.negative.subnormal.min], expected: kUnboundedBounds },
- { input: [0, constants.positive.min], expected: kUnboundedBounds },
- { input: [0, constants.negative.max], expected: kUnboundedBounds },
- { input: [0, constants.positive.max], expected: kUnboundedBounds },
- { input: [0, constants.negative.min], expected: kUnboundedBounds },
- { input: [0, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [0, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [0, 1], expected: kUnboundedBounds },
- { input: [constants.positive.subnormal.max, 1], expected: kUnboundedBounds },
- { input: [constants.negative.subnormal.min, 1], expected: kUnboundedBounds },
-
- // Very large |x| values should cause kUnboundedBounds to be returned, due to the restrictions on division
- { input: [1, constants.positive.max], expected: kUnboundedBounds },
- { input: [1, constants.positive.nearest_max], expected: kUnboundedBounds },
- { input: [1, constants.negative.min], expected: kUnboundedBounds },
- { input: [1, constants.negative.nearest_min], expected: kUnboundedBounds },
+ { input: [0, 0], expected: kUnboundedEndpoints },
+ { input: [0, constants.positive.subnormal.max], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.subnormal.min], expected: kUnboundedEndpoints },
+ { input: [0, constants.positive.min], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.max], expected: kUnboundedEndpoints },
+ { input: [0, constants.positive.max], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.min], expected: kUnboundedEndpoints },
+ { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [0, 1], expected: kUnboundedEndpoints },
+ { input: [constants.positive.subnormal.max, 1], expected: kUnboundedEndpoints },
+ { input: [constants.negative.subnormal.min, 1], expected: kUnboundedEndpoints },
+
+ // Very large |x| values should cause kUnboundedEndpoints to be returned, due to the restrictions on division
+ { input: [1, constants.positive.max], expected: kUnboundedEndpoints },
+ { input: [1, constants.positive.nearest_max], expected: kUnboundedEndpoints },
+ { input: [1, constants.negative.min], expected: kUnboundedEndpoints },
+ { input: [1, constants.negative.nearest_min], expected: kUnboundedEndpoints },
];
})
)
@@ -4290,25 +4290,25 @@ g.test('distanceIntervalScalar')
{ input: [-10.0, 0], expected: kRootSumSquareExpectionInterval[p.trait]['[10]'] }, // ~10
{ input: [0, -10.0], expected: kRootSumSquareExpectionInterval[p.trait]['[10]'] }, // ~10
- // distance(x, y), where x - y = 0 has an acceptance interval of kUnboundedBounds,
- // because distance(x, y) = length(x - y), and length(0) = kUnboundedBounds
- { input: [0, 0], expected: kUnboundedBounds },
- { input: [1.0, 1.0], expected: kUnboundedBounds },
- { input: [-1.0, -1.0], expected: kUnboundedBounds },
+ // distance(x, y), where x - y = 0 has an acceptance interval of kUnboundedEndpoints,
+ // because distance(x, y) = length(x - y), and length(0) = kUnboundedEndpoints
+ { input: [0, 0], expected: kUnboundedEndpoints },
+ { input: [1.0, 1.0], expected: kUnboundedEndpoints },
+ { input: [-1.0, -1.0], expected: kUnboundedEndpoints },
// Subnormal Cases
- { input: [constants.negative.subnormal.min, 0], expected: kUnboundedBounds },
- { input: [constants.negative.subnormal.max, 0], expected: kUnboundedBounds },
- { input: [constants.positive.subnormal.min, 0], expected: kUnboundedBounds },
- { input: [constants.positive.subnormal.max, 0], expected: kUnboundedBounds },
+ { input: [constants.negative.subnormal.min, 0], expected: kUnboundedEndpoints },
+ { input: [constants.negative.subnormal.max, 0], expected: kUnboundedEndpoints },
+ { input: [constants.positive.subnormal.min, 0], expected: kUnboundedEndpoints },
+ { input: [constants.positive.subnormal.max, 0], expected: kUnboundedEndpoints },
// Edge cases
- { input: [constants.positive.infinity, 0], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, 0], expected: kUnboundedBounds },
- { input: [constants.negative.min, 0], expected: kUnboundedBounds },
- { input: [constants.negative.max, 0], expected: kUnboundedBounds },
- { input: [constants.positive.min, 0], expected: kUnboundedBounds },
- { input: [constants.positive.max, 0], expected: kUnboundedBounds },
+ { input: [constants.positive.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.negative.min, 0], expected: kUnboundedEndpoints },
+ { input: [constants.negative.max, 0], expected: kUnboundedEndpoints },
+ { input: [constants.positive.min, 0], expected: kUnboundedEndpoints },
+ { input: [constants.positive.max, 0], expected: kUnboundedEndpoints },
];
})
)
@@ -4371,13 +4371,10 @@ const kDivisionInterval64BitsNormalCases = {
g.test('divisionInterval')
.params(u =>
u
- .combine('trait', ['abstract', 'f32', 'f16'] as const)
+ .combine('trait', ['f32', 'f16'] as const)
.beginSubcases()
.expandWithParams<ScalarPairToIntervalCase>(p => {
- // This is a ULP based interval, so abstract should behave like f32, so
- // swizzling the trait as needed.
- const trait = p.trait === 'abstract' ? 'f32' : p.trait;
- const fp = FP[trait];
+ const fp = FP[p.trait];
const constants = fp.constants();
// prettier-ignore
return [
@@ -4394,26 +4391,23 @@ g.test('divisionInterval')
{ input: [-4, -2], expected: 2 },
// 64-bit normals that can not be exactly represented
- ...kDivisionInterval64BitsNormalCases[trait],
+ ...kDivisionInterval64BitsNormalCases[p.trait],
// Denominator out of range
- { input: [1, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [1, constants.negative.infinity], 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 },
- { input: [1, constants.positive.max], expected: kUnboundedBounds },
- { input: [1, constants.negative.min], expected: kUnboundedBounds },
- { input: [1, 0], expected: kUnboundedBounds },
- { input: [1, constants.positive.subnormal.max], expected: kUnboundedBounds },
+ { input: [1, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [1, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [1, constants.positive.max], expected: kUnboundedEndpoints },
+ { input: [1, constants.negative.min], expected: kUnboundedEndpoints },
+ { input: [1, 0], expected: kUnboundedEndpoints },
+ { input: [1, constants.positive.subnormal.max], expected: kUnboundedEndpoints },
];
})
)
.fn(t => {
- // This is a ULP based interval, so abstract should behave like f32, so
- // swizzling the trait as needed for calculating the expected result.
- const trait = t.params.trait === 'abstract' ? 'f32' : t.params.trait;
- const fp = FP[trait];
+ const fp = FP[t.params.trait];
const error = (n: number): number => {
return 2.5 * fp.oneULP(n);
@@ -4421,7 +4415,6 @@ g.test('divisionInterval')
const [x, y] = t.params.input;
- // Do not swizzle here, so the correct implementation under test is called.
const expected = FP[t.params.trait].toInterval(applyError(t.params.expected, error));
const got = FP[t.params.trait].divisionInterval(x, y);
t.expect(
@@ -4452,11 +4445,11 @@ const kLdexpIntervalCases = {
// e2 + bias <= 0, expect correctly rounded intervals.
{ input: [2 ** 120, -130], expected: 2 ** -10 },
// Out of Bounds
- { input: [1, 128], expected: kUnboundedBounds },
- { input: [-1, 128], expected: kUnboundedBounds },
- { input: [100, 126], expected: kUnboundedBounds },
- { input: [-100, 126], expected: kUnboundedBounds },
- { input: [2 ** 100, 100], expected: kUnboundedBounds },
+ { input: [1, 128], expected: kUnboundedEndpoints },
+ { input: [-1, 128], expected: kUnboundedEndpoints },
+ { input: [100, 126], expected: kUnboundedEndpoints },
+ { input: [-100, 126], expected: kUnboundedEndpoints },
+ { input: [2 ** 100, 100], expected: kUnboundedEndpoints },
] as ScalarPairToIntervalCase[],
f16: [
// 64-bit normals
@@ -4478,25 +4471,66 @@ const kLdexpIntervalCases = {
// e2 + bias <= 0, expect correctly rounded intervals.
{ input: [2 ** 12, -18], expected: 2 ** -6 },
// Out of Bounds
- { input: [1, 16], expected: kUnboundedBounds },
- { input: [-1, 16], expected: kUnboundedBounds },
- { input: [100, 14], expected: kUnboundedBounds },
- { input: [-100, 14], expected: kUnboundedBounds },
- { input: [2 ** 10, 10], expected: kUnboundedBounds },
+ { input: [1, 16], expected: kUnboundedEndpoints },
+ { input: [-1, 16], expected: kUnboundedEndpoints },
+ { input: [100, 14], expected: kUnboundedEndpoints },
+ { input: [-100, 14], expected: kUnboundedEndpoints },
+ { input: [2 ** 10, 10], expected: kUnboundedEndpoints },
+ ] as ScalarPairToIntervalCase[],
+ abstract: [
+ // Edge Cases
+ // 1.9999999999999997779553950749686919152736663818359375 * 2 ** 1023 = f64.positive.max
+ {
+ input: [1.9999999999999997779553950749686919152736663818359375, 1023],
+ expected: kValue.f64.positive.max,
+ },
+ // f64.positive.min = 1 * 2 ** -1022
+ { input: [1, -1022], expected: kValue.f64.positive.min },
+ // f64.positive.subnormal.max = 1.9999999999999997779553950749686919152736663818359375 * 2 ** -1022
+ {
+ input: [0.9999999999999997779553950749686919152736663818359375, -1022],
+ expected: [0, kValue.f64.positive.subnormal.max],
+ },
+ // f64.positive.subnormal.min = 0.0000000000000002220446049250313080847263336181640625 * 2 ** -1022
+ {
+ input: [0.0000000000000002220446049250313080847263336181640625, -1022],
+ expected: [0, kValue.f64.positive.subnormal.min],
+ },
+ {
+ input: [-0.0000000000000002220446049250313080847263336181640625, -1022],
+ expected: [kValue.f64.negative.subnormal.max, 0],
+ },
+ {
+ input: [-0.9999999999999997779553950749686919152736663818359375, -1022],
+ expected: [kValue.f64.negative.subnormal.min, 0],
+ },
+ { input: [-1, -1022], expected: kValue.f64.negative.max },
+ {
+ input: [-1.9999999999999997779553950749686919152736663818359375, 1023],
+ expected: kValue.f64.negative.min,
+ },
+ // e2 + bias <= 0, expect correctly rounded intervals.
+ { input: [2 ** 120, -130], expected: 2 ** -10 },
+ // Out of Bounds
+ { input: [1, 1024], expected: kUnboundedEndpoints },
+ { input: [-1, 1024], expected: kUnboundedEndpoints },
+ { input: [100, 1024], expected: kUnboundedEndpoints },
+ { input: [-100, 1024], expected: kUnboundedEndpoints },
+ { input: [2 ** 100, 1000], expected: kUnboundedEndpoints },
] as ScalarPairToIntervalCase[],
} as const;
g.test('ldexpInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16'] as const)
+ .combine('trait', ['f32', 'f16', 'abstract'] as const)
.beginSubcases()
.expandWithParams<ScalarPairToIntervalCase>(p => {
const trait = FP[p.trait];
const constants = trait.constants();
// prettier-ignore
return [
- // always exactly represeantable cases
+ // always exactly representable cases
{ input: [0, 0], expected: 0 },
{ input: [0, 1], expected: 0 },
{ input: [0, -1], expected: 0 },
@@ -4512,8 +4546,8 @@ g.test('ldexpInterval')
{ input: [constants.positive.max, kValue.i32.negative.min], expected: 0 },
{ input: [constants.negative.min, kValue.i32.negative.min], expected: 0 },
// Out of Bounds
- { input: [constants.positive.max, kValue.i32.positive.max], expected: kUnboundedBounds },
- { input: [constants.negative.min, kValue.i32.positive.max], expected: kUnboundedBounds },
+ { input: [constants.positive.max, kValue.i32.positive.max], expected: kUnboundedEndpoints },
+ { input: [constants.negative.min, kValue.i32.positive.max], expected: kUnboundedEndpoints },
];
})
)
@@ -4572,14 +4606,14 @@ g.test('maxInterval')
{ input: [constants.negative.subnormal.min, constants.positive.subnormal.max], expected: [constants.negative.subnormal.min, constants.positive.subnormal.max] },
// 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 },
+ { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
];
})
)
@@ -4638,14 +4672,14 @@ g.test('minInterval')
{ input: [constants.negative.subnormal.min, constants.positive.subnormal.max], expected: [constants.negative.subnormal.min, constants.positive.subnormal.max] },
// 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 },
+ { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
];
})
)
@@ -4740,22 +4774,22 @@ g.test('multiplicationInterval')
...kMultiplicationInterval64BitsNormalCases[p.trait],
// Infinities
- { input: [0, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [1, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [-1, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [0, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [1, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [-1, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedBounds },
+ { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [1, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [-1, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [1, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [-1, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
// Edges
- { input: [constants.positive.max, constants.positive.max], expected: kUnboundedBounds },
- { input: [constants.negative.min, constants.negative.min], expected: kUnboundedBounds },
- { input: [constants.positive.max, constants.negative.min], expected: kUnboundedBounds },
- { input: [constants.negative.min, constants.positive.max], expected: kUnboundedBounds },
+ { input: [constants.positive.max, constants.positive.max], expected: kUnboundedEndpoints },
+ { input: [constants.negative.min, constants.negative.min], expected: kUnboundedEndpoints },
+ { input: [constants.positive.max, constants.negative.min], expected: kUnboundedEndpoints },
+ { input: [constants.negative.min, constants.positive.max], expected: kUnboundedEndpoints },
];
})
)
@@ -4808,11 +4842,11 @@ g.test('powInterval')
const constants = trait.constants();
// prettier-ignore
return [
- { input: [-1, 0], expected: kUnboundedBounds },
- { input: [0, 0], expected: kUnboundedBounds },
- { input: [0, 1], expected: kUnboundedBounds },
- { input: [1, constants.positive.max], expected: kUnboundedBounds },
- { input: [constants.positive.max, 1], expected: kUnboundedBounds },
+ { input: [-1, 0], expected: kUnboundedEndpoints },
+ { input: [0, 0], expected: kUnboundedEndpoints },
+ { input: [0, 1], expected: kUnboundedEndpoints },
+ { input: [1, constants.positive.max], expected: kUnboundedEndpoints },
+ { input: [constants.positive.max, 1], expected: kUnboundedEndpoints },
...kPowIntervalCases[p.trait],
];
@@ -4848,7 +4882,7 @@ const kRemainderCases = {
g.test('remainderInterval')
.params(u =>
u
- .combine('trait', ['abstract', 'f32', 'f16'] as const)
+ .combine('trait', ['f32', 'f16'] as const)
.beginSubcases()
.expandWithParams<ScalarPairToIntervalCase>(p => {
const trait = kFPTraitForULP[p.trait];
@@ -4878,15 +4912,15 @@ g.test('remainderInterval')
{ input: [1.125, 1], expected: 0.125 },
// Denominator out of range
- { input: [1, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [1, constants.negative.infinity], 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 },
- { input: [1, constants.positive.max], expected: kUnboundedBounds },
- { input: [1, constants.negative.min], expected: kUnboundedBounds },
- { input: [1, 0], expected: kUnboundedBounds },
- { input: [1, constants.positive.subnormal.max], expected: kUnboundedBounds },
+ { input: [1, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [1, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [1, constants.positive.max], expected: kUnboundedEndpoints },
+ { input: [1, constants.negative.min], expected: kUnboundedEndpoints },
+ { input: [1, 0], expected: kUnboundedEndpoints },
+ { input: [1, constants.positive.subnormal.max], expected: kUnboundedEndpoints },
];
})
)
@@ -4904,7 +4938,7 @@ g.test('remainderInterval')
g.test('stepInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16'] as const)
+ .combine('trait', ['f32', 'f16', 'abstract'] as const)
.beginSubcases()
.expandWithParams<ScalarPairToIntervalCase>(p => {
const constants = FP[p.trait].constants();
@@ -4922,12 +4956,17 @@ g.test('stepInterval')
{ input: [1, -1], expected: 0 },
// 64-bit normals
- { input: [0.1, 0.1], expected: [0, 1] },
+ // number is f64 internally, so the value representing the literal
+ // 0.1/-0.1 will always be exactly representable in AbstractFloat,
+ // since AF is also f64 internally.
+ // It is impossible with normals to cause the rounding ambiguity that
+ // causes the 0 or 1 result.
+ { input: [0.1, 0.1], expected: p.trait === 'abstract' ? 1 : [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.1, -0.1], expected: p.trait === 'abstract' ? 1 : [0, 1] },
{ input: [0, -0.1], expected: 0 },
{ input: [-0.1, 0], expected: 1 },
{ input: [-0.1, -1], expected: 0 },
@@ -4962,14 +5001,14 @@ g.test('stepInterval')
{ input: [constants.positive.subnormal.max, constants.negative.subnormal.min], expected: [0, 1] },
// 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 },
+ { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
];
})
)
@@ -5060,14 +5099,14 @@ g.test('subtractionInterval')
{ input: [0, constants.negative.subnormal.min], expected: [0, constants.positive.subnormal.max] },
// 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 },
+ { input: [0, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, 0], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
];
})
)
@@ -5084,7 +5123,7 @@ g.test('subtractionInterval')
interface ScalarTripleToIntervalCase {
input: [number, number, number];
- expected: number | IntervalBounds;
+ expected: number | IntervalEndpoints;
}
g.test('clampMedianInterval')
@@ -5127,10 +5166,10 @@ g.test('clampMedianInterval')
{ input: [constants.positive.max, constants.positive.max, constants.positive.subnormal.min], expected: constants.positive.max },
// 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 },
+ { input: [0, 1, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
];
})
)
@@ -5185,10 +5224,10 @@ g.test('clampMinMaxInterval')
{ input: [constants.positive.max, constants.positive.max, constants.positive.subnormal.min], expected: [0, constants.positive.subnormal.min] },
// 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 },
+ { input: [0, 1, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
];
})
)
@@ -5240,21 +5279,12 @@ const kFmaIntervalCases = {
// minimum case: -1 * [subnormal ulp] + -1 * [subnormal ulp] rounded to [-2 * [subnormal ulp], 0],
// maximum case: -0.0 + -0.0 = 0.
{ input: [kValue.f16.positive.subnormal.max, kValue.f16.negative.subnormal.min, kValue.f16.negative.subnormal.max], expected: [-2 * FP['f16'].oneULP(0, 'no-flush'), 0] }, ] as ScalarTripleToIntervalCase[],
- abstract: [
- // These operations break down in the CTS, because `number` is a f64 under the hood, so precision is sometimes lost
- // if intermediate results are closer to 0 than the smallest subnormal will be precisely 0.
- // See https://github.com/gpuweb/cts/issues/2993 for details
- { input: [kValue.f64.positive.subnormal.max, kValue.f64.positive.subnormal.max, 0], expected: 0 },
- { input: [kValue.f64.positive.subnormal.max, kValue.f64.positive.subnormal.max, kValue.f64.positive.subnormal.max], expected: [0, kValue.f64.positive.subnormal.max] },
- { input: [kValue.f64.positive.subnormal.max, kValue.f64.positive.subnormal.min, kValue.f64.negative.subnormal.max], expected: [kValue.f64.negative.subnormal.max, 0] },
- { input: [kValue.f64.positive.subnormal.max, kValue.f64.negative.subnormal.min, kValue.f64.negative.subnormal.max], expected: [kValue.f64.negative.subnormal.max, 0] },
- ] as ScalarTripleToIntervalCase[],
} as const;
g.test('fmaInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16', 'abstract'] as const)
+ .combine('trait', ['f32', 'f16'] as const)
.beginSubcases()
.expandWithParams<ScalarTripleToIntervalCase>(p => {
const trait = FP[p.trait];
@@ -5286,11 +5316,11 @@ g.test('fmaInterval')
{ input: [0, constants.positive.subnormal.max, constants.positive.subnormal.max], expected: [0, constants.positive.subnormal.max] },
// 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 },
- { input: [constants.positive.max, constants.positive.max, constants.positive.subnormal.min], expected: kUnboundedBounds },
+ { input: [0, 1, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.max, constants.positive.max, constants.positive.subnormal.min], expected: kUnboundedEndpoints },
...kFmaIntervalCases[p.trait],
];
})
@@ -5312,55 +5342,62 @@ g.test('fmaInterval')
// prettier-ignore
const kMixImpreciseIntervalCases = {
f32: [
- // [0.0, 1.0] cases
- { input: [0.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0x3fb9_9999_8000_0000n), reinterpretU64AsF64(0x3fb9_9999_a000_0000n)] }, // ~0.1
- { input: [0.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fec_cccc_c000_0000n), reinterpretU64AsF64(0x3fec_cccc_e000_0000n)] }, // ~0.9
- // [1.0, 0.0] cases
- { input: [1.0, 0.0, 0.1], expected: [reinterpretU64AsF64(0x3fec_cccc_c000_0000n), reinterpretU64AsF64(0x3fec_cccc_e000_0000n)] }, // ~0.9
- { input: [1.0, 0.0, 0.9], expected: [reinterpretU64AsF64(0x3fb9_9999_0000_0000n), reinterpretU64AsF64(0x3fb9_999a_0000_0000n)] }, // ~0.1
- // [0.0, 10.0] cases
- { input: [0.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x3fef_ffff_e000_0000n), reinterpretU64AsF64(0x3ff0_0000_2000_0000n)] }, // ~1
- { input: [0.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4021_ffff_e000_0000n), reinterpretU64AsF64(0x4022_0000_2000_0000n)] }, // ~9
- // [2.0, 10.0] cases
- { input: [2.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x4006_6666_6000_0000n), reinterpretU64AsF64(0x4006_6666_8000_0000n)] }, // ~2.8
- { input: [2.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4022_6666_6000_0000n), reinterpretU64AsF64(0x4022_6666_8000_0000n)] }, // ~9.2
- // [-1.0, 1.0] cases
- { input: [-1.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0xbfe9_9999_a000_0000n), reinterpretU64AsF64(0xbfe9_9999_8000_0000n)] }, // ~-0.8
- { input: [-1.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fe9_9999_8000_0000n), reinterpretU64AsF64(0x3fe9_9999_c000_0000n)] }, // ~0.8
-
- // Showing how precise and imprecise versions diff
- // Note that this expectation is 0 only in f32 as 10.0 is much smaller that f32.negative.min,
- // So that 10 - f32.negative.min == f32.negative.min even in f64. But for f16, there is not
- // a exactly-represenatble f16 value v that make v - f16.negative.min == f16.negative.min
- // in f64, in fact that require v being smaller than 2**-37.
- { input: [kValue.f32.negative.min, 10.0, 1.0], expected: 0.0 },
- // -10.0 is the same, much smaller than f32.negative.min
- { input: [kValue.f32.negative.min, -10.0, 1.0], expected: 0.0 },
+ // [0.0, 1.0] cases
+ { input: [0.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0x3fb9_9999_8000_0000n), reinterpretU64AsF64(0x3fb9_9999_a000_0000n)] }, // ~0.1
+ { input: [0.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fec_cccc_c000_0000n), reinterpretU64AsF64(0x3fec_cccc_e000_0000n)] }, // ~0.9
+ // [1.0, 0.0] cases
+ { input: [1.0, 0.0, 0.1], expected: [reinterpretU64AsF64(0x3fec_cccc_c000_0000n), reinterpretU64AsF64(0x3fec_cccc_e000_0000n)] }, // ~0.9
+ { input: [1.0, 0.0, 0.9], expected: [reinterpretU64AsF64(0x3fb9_9999_0000_0000n), reinterpretU64AsF64(0x3fb9_999a_0000_0000n)] }, // ~0.1
+ // [0.0, 10.0] cases
+ { input: [0.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x3fef_ffff_e000_0000n), reinterpretU64AsF64(0x3ff0_0000_2000_0000n)] }, // ~1
+ { input: [0.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4021_ffff_e000_0000n), reinterpretU64AsF64(0x4022_0000_2000_0000n)] }, // ~9
+ // [2.0, 10.0] cases
+ { input: [2.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x4006_6666_6000_0000n), reinterpretU64AsF64(0x4006_6666_8000_0000n)] }, // ~2.8
+ { input: [2.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4022_6666_6000_0000n), reinterpretU64AsF64(0x4022_6666_8000_0000n)] }, // ~9.2
+ // [-1.0, 1.0] cases
+ { input: [-1.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0xbfe9_9999_a000_0000n), reinterpretU64AsF64(0xbfe9_9999_8000_0000n)] }, // ~-0.8
+ { input: [-1.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fe9_9999_8000_0000n), reinterpretU64AsF64(0x3fe9_9999_c000_0000n)] }, // ~0.8
+
+ // Showing how precise and imprecise versions diff
+ // Note that this expectation is 0 in f32 as |10.0| is much smaller than
+ // |f32.negative.min|.
+ // So that 10 - f32.negative.min == -f32.negative.min even in f64.
+ { input: [kValue.f32.negative.min, 10.0, 1.0], expected: 0.0 },
+ // -10.0 is the same, much smaller than f32.negative.min
+ { input: [kValue.f32.negative.min, -10.0, 1.0], expected: 0.0 },
+ { input: [kValue.f32.negative.min, 10.0, 5.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f32.negative.min, -10.0, 5.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f32.negative.min, 10.0, 0.5], expected: reinterpretU32AsF32(0xfeffffff) },
+ { input: [kValue.f32.negative.min, -10.0, 0.5], expected: reinterpretU32AsF32(0xfeffffff) },
] as ScalarTripleToIntervalCase[],
f16: [
- // [0.0, 1.0] cases
- { input: [0.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0x3fb9_9800_0000_0000n), reinterpretU64AsF64(0x3fb9_9c00_0000_0000n)] }, // ~0.1
- { input: [0.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fec_cc00_0000_0000n), reinterpretU64AsF64(0x3fec_d000_0000_0000n)] }, // ~0.9
- // [1.0, 0.0] cases
- { input: [1.0, 0.0, 0.1], expected: [reinterpretU64AsF64(0x3fec_cc00_0000_0000n), reinterpretU64AsF64(0x3fec_d000_0000_0000n)] }, // ~0.9
- { input: [1.0, 0.0, 0.9], expected: [reinterpretU64AsF64(0x3fb9_8000_0000_0000n), reinterpretU64AsF64(0x3fb9_a000_0000_0000n)] }, // ~0.1
- // [0.0, 10.0] cases
- { input: [0.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x3fef_fc00_0000_0000n), reinterpretU64AsF64(0x3ff0_0400_0000_0000n)] }, // ~1
- { input: [0.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4021_fc00_0000_0000n), reinterpretU64AsF64(0x4022_0400_0000_0000n)] }, // ~9
- // [2.0, 10.0] cases
- { input: [2.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x4006_6400_0000_0000n), reinterpretU64AsF64(0x4006_6800_0000_0000n)] }, // ~2.8
- { input: [2.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4022_6400_0000_0000n), reinterpretU64AsF64(0x4022_6800_0000_0000n)] }, // ~9.2
- // [-1.0, 1.0] cases
- { input: [-1.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0xbfe9_9c00_0000_0000n), reinterpretU64AsF64(0xbfe9_9800_0000_0000n)] }, // ~-0.8
- { input: [-1.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fe9_9800_0000_0000n), reinterpretU64AsF64(0x3fe9_a000_0000_0000n)] }, // ~0.8
-
- // Showing how precise and imprecise versions diff
- // In imprecise version, we compute (y - x), where y = 10 and x = -65504, the result is 65514
- // and cause an overflow in f16.
- { input: [kValue.f16.negative.min, 10.0, 1.0], expected: kUnboundedBounds },
- // (y - x) * 1.0, where y = -10 and x = -65504, the result is 65494 rounded to 65472 or 65504.
- // The result is -65504 + 65472 = -32 or -65504 + 65504 = 0.
- { input: [kValue.f16.negative.min, -10.0, 1.0], expected: [-32, 0] },
+ // [0.0, 1.0] cases
+ { input: [0.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0x3fb9_9800_0000_0000n), reinterpretU64AsF64(0x3fb9_9c00_0000_0000n)] }, // ~0.1
+ { input: [0.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fec_cc00_0000_0000n), reinterpretU64AsF64(0x3fec_d000_0000_0000n)] }, // ~0.9
+ // [1.0, 0.0] cases
+ { input: [1.0, 0.0, 0.1], expected: [reinterpretU64AsF64(0x3fec_cc00_0000_0000n), reinterpretU64AsF64(0x3fec_d000_0000_0000n)] }, // ~0.9
+ { input: [1.0, 0.0, 0.9], expected: [reinterpretU64AsF64(0x3fb9_8000_0000_0000n), reinterpretU64AsF64(0x3fb9_a000_0000_0000n)] }, // ~0.1
+ // [0.0, 10.0] cases
+ { input: [0.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x3fef_fc00_0000_0000n), reinterpretU64AsF64(0x3ff0_0400_0000_0000n)] }, // ~1
+ { input: [0.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4021_fc00_0000_0000n), reinterpretU64AsF64(0x4022_0400_0000_0000n)] }, // ~9
+ // [2.0, 10.0] cases
+ { input: [2.0, 10.0, 0.1], expected: [reinterpretU64AsF64(0x4006_6400_0000_0000n), reinterpretU64AsF64(0x4006_6800_0000_0000n)] }, // ~2.8
+ { input: [2.0, 10.0, 0.9], expected: [reinterpretU64AsF64(0x4022_6400_0000_0000n), reinterpretU64AsF64(0x4022_6800_0000_0000n)] }, // ~9.2
+ // [-1.0, 1.0] cases
+ { input: [-1.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0xbfe9_9c00_0000_0000n), reinterpretU64AsF64(0xbfe9_9800_0000_0000n)] }, // ~-0.8
+ { input: [-1.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fe9_9800_0000_0000n), reinterpretU64AsF64(0x3fe9_a000_0000_0000n)] }, // ~0.8
+
+ // Showing how precise and imprecise versions diff
+ // In imprecise version, we compute (y - x), where y = 10 and x = -65504, the result is 65514
+ // and cause an overflow in f16.
+ { input: [kValue.f16.negative.min, 10.0, 1.0], expected: kUnboundedEndpoints },
+ // (y - x) * 1.0, where y = -10 and x = -65504, the result is 65494 rounded to 65472 or 65504.
+ // The result is -65504 + 65472 = -32 or -65504 + 65504 = 0.
+ { input: [kValue.f16.negative.min, -10.0, 1.0], expected: [-32, 0] },
+ { input: [kValue.f16.negative.min, 10.0, 5.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f16.negative.min, -10.0, 5.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f16.negative.min, 10.0, 0.5], expected: kUnboundedEndpoints },
+ { input: [kValue.f16.negative.min, -10.0, 0.5], expected: [-32768.0, -32752.0] },
] as ScalarTripleToIntervalCase[],
} as const;
@@ -5412,19 +5449,16 @@ g.test('mixImpreciseInterval')
{ input: [-1.0, 1.0, 2.0], expected: 3.0 },
// Infinities
- { input: [0.0, constants.positive.infinity, 0.5], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, 0.0, 0.5], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, 1.0, 0.5], expected: kUnboundedBounds },
- { input: [1.0, constants.negative.infinity, 0.5], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity, 0.5], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.negative.infinity, 0.5], expected: kUnboundedBounds },
- { input: [0.0, 1.0, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [1.0, 0.0, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [0.0, 1.0, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [1.0, 0.0, constants.positive.infinity], expected: kUnboundedBounds },
-
- // The [negative.min, +/-10.0, 1.0] cases has different result for different trait on
- // imprecise version.
+ { input: [0.0, constants.positive.infinity, 0.5], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, 0.0, 0.5], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, 1.0, 0.5], expected: kUnboundedEndpoints },
+ { input: [1.0, constants.negative.infinity, 0.5], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity, 0.5], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity, 0.5], expected: kUnboundedEndpoints },
+ { input: [0.0, 1.0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [1.0, 0.0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [0.0, 1.0, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [1.0, 0.0, constants.positive.infinity], expected: kUnboundedEndpoints },
];
})
)
@@ -5459,6 +5493,17 @@ const kMixPreciseIntervalCases = {
// [-1.0, 1.0] cases
{ input: [-1.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0xbfe9_9999_c000_0000n), reinterpretU64AsF64(0xbfe9_9999_8000_0000n)] }, // ~-0.8
{ input: [-1.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fe9_9999_8000_0000n), reinterpretU64AsF64(0x3fe9_9999_c000_0000n)] }, // ~0.8
+
+ // Showing how precise and imprecise versions diff
+ { input: [kValue.f32.negative.min, 10.0, 1.0], expected: 10 },
+ { input: [kValue.f32.negative.min, -10.0, 1.0], expected: -10 },
+ { input: [kValue.f32.negative.min, 10.0, 5.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f32.negative.min, -10.0, 5.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f32.negative.min, 10.0, 0.5], expected: reinterpretU32AsF32(0xfeffffff) },
+ { input: [kValue.f32.negative.min, -10.0, 0.5], expected: reinterpretU32AsF32(0xfeffffff) },
+
+ // Intermediate OOB
+ { input: [1.0, 2.0, kPlusOneULPFunctions['f32'](kValue.f32.positive.max / 2)], expected: kUnboundedEndpoints },
] as ScalarTripleToIntervalCase[],
f16: [
// [0.0, 1.0] cases
@@ -5476,6 +5521,17 @@ const kMixPreciseIntervalCases = {
// [-1.0, 1.0] cases
{ input: [-1.0, 1.0, 0.1], expected: [reinterpretU64AsF64(0xbfe9_a000_0000_0000n), reinterpretU64AsF64(0xbfe9_9800_0000_0000n)] }, // ~-0.8
{ input: [-1.0, 1.0, 0.9], expected: [reinterpretU64AsF64(0x3fe9_9800_0000_0000n), reinterpretU64AsF64(0x3fe9_a000_0000_0000n)] }, // ~0.8
+
+ // Showing how precise and imprecise versions diff
+ { input: [kValue.f64.negative.min, 10.0, 1.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f64.negative.min, -10.0, 1.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f64.negative.min, 10.0, 5.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f64.negative.min, -10.0, 5.0], expected: kUnboundedEndpoints },
+ { input: [kValue.f64.negative.min, 10.0, 0.5], expected: kUnboundedEndpoints },
+ { input: [kValue.f64.negative.min, -10.0, 0.5], expected: kUnboundedEndpoints },
+
+ // Intermediate OOB
+ { input: [1.0, 2.0, kPlusOneULPFunctions['f16'](kValue.f16.positive.max / 2)], expected: kUnboundedEndpoints },
] as ScalarTripleToIntervalCase[],
} as const;
@@ -5527,20 +5583,16 @@ g.test('mixPreciseInterval')
{ input: [-1.0, 1.0, 2.0], expected: 3.0 },
// Infinities
- { input: [0.0, constants.positive.infinity, 0.5], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, 0.0, 0.5], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, 1.0, 0.5], expected: kUnboundedBounds },
- { input: [1.0, constants.negative.infinity, 0.5], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, constants.positive.infinity, 0.5], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, constants.negative.infinity, 0.5], expected: kUnboundedBounds },
- { input: [0.0, 1.0, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [1.0, 0.0, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [0.0, 1.0, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [1.0, 0.0, constants.positive.infinity], expected: kUnboundedBounds },
-
- // Showing how precise and imprecise versions diff
- { input: [constants.negative.min, 10.0, 1.0], expected: 10.0 },
- { input: [constants.negative.min, -10.0, 1.0], expected: -10.0 },
+ { input: [0.0, constants.positive.infinity, 0.5], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, 0.0, 0.5], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, 1.0, 0.5], expected: kUnboundedEndpoints },
+ { input: [1.0, constants.negative.infinity, 0.5], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, constants.positive.infinity, 0.5], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, constants.negative.infinity, 0.5], expected: kUnboundedEndpoints },
+ { input: [0.0, 1.0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [1.0, 0.0, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [0.0, 1.0, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [1.0, 0.0, constants.positive.infinity], expected: kUnboundedEndpoints },
];
})
)
@@ -5622,18 +5674,18 @@ g.test('smoothStepInterval')
{ input: [0, 1, -10], expected: 0 },
// Subnormals
- { input: [0, constants.positive.subnormal.max, 1], expected: kUnboundedBounds },
- { input: [0, constants.positive.subnormal.min, 1], expected: kUnboundedBounds },
- { input: [0, constants.negative.subnormal.max, 1], expected: kUnboundedBounds },
- { input: [0, constants.negative.subnormal.min, 1], expected: kUnboundedBounds },
+ { input: [0, constants.positive.subnormal.max, 1], expected: kUnboundedEndpoints },
+ { input: [0, constants.positive.subnormal.min, 1], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.subnormal.max, 1], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.subnormal.min, 1], expected: kUnboundedEndpoints },
// Infinities
- { input: [0, 2, constants.positive.infinity], expected: kUnboundedBounds },
- { input: [0, 2, constants.negative.infinity], expected: kUnboundedBounds },
- { input: [constants.positive.infinity, 2, 1], expected: kUnboundedBounds },
- { input: [constants.negative.infinity, 2, 1], expected: kUnboundedBounds },
- { input: [0, constants.positive.infinity, 1], expected: kUnboundedBounds },
- { input: [0, constants.negative.infinity, 1], expected: kUnboundedBounds },
+ { input: [0, 2, constants.positive.infinity], expected: kUnboundedEndpoints },
+ { input: [0, 2, constants.negative.infinity], expected: kUnboundedEndpoints },
+ { input: [constants.positive.infinity, 2, 1], expected: kUnboundedEndpoints },
+ { input: [constants.negative.infinity, 2, 1], expected: kUnboundedEndpoints },
+ { input: [0, constants.positive.infinity, 1], expected: kUnboundedEndpoints },
+ { input: [0, constants.negative.infinity, 1], expected: kUnboundedEndpoints },
];
})
)
@@ -5650,7 +5702,7 @@ g.test('smoothStepInterval')
interface ScalarToVectorCase {
input: number;
- expected: (number | IntervalBounds)[];
+ expected: (number | IntervalEndpoints)[];
}
g.test('unpack2x16floatInterval')
@@ -5674,8 +5726,8 @@ g.test('unpack2x16floatInterval')
{ input: 0x000083ff, expected: [[kValue.f16.negative.subnormal.min, 0], 0] },
// f16 out of bounds
- { input: 0x7c000000, expected: [kUnboundedBounds, kUnboundedBounds] },
- { input: 0xffff0000, expected: [kUnboundedBounds, kUnboundedBounds] },
+ { input: 0x7c000000, expected: [kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: 0xffff0000, expected: [kUnboundedEndpoints, kUnboundedEndpoints] },
]
)
.fn(t => {
@@ -5691,24 +5743,24 @@ g.test('unpack2x16floatInterval')
// magic numbers that don't pollute the global namespace or have unwieldy long
// names.
{
- const kZeroBounds: IntervalBounds = [
+ const kZeroEndpoints: IntervalEndpoints = [
reinterpretU32AsF32(0x81400000),
reinterpretU32AsF32(0x01400000),
];
- const kOneBoundsSnorm: IntervalBounds = [
+ const kOneEndpointsSnorm: IntervalEndpoints = [
reinterpretU64AsF64(0x3fef_ffff_a000_0000n),
reinterpretU64AsF64(0x3ff0_0000_3000_0000n),
];
- const kNegOneBoundsSnorm: IntervalBounds = [
+ const kNegOneEndpointsSnorm: IntervalEndpoints = [
reinterpretU64AsF64(0xbff0_0000_3000_0000n),
reinterpretU64AsF64(0xbfef_ffff_a000_0000n),
];
- const kHalfBounds2x16snorm: IntervalBounds = [
+ const kHalfEndpoints2x16snorm: IntervalEndpoints = [
reinterpretU64AsF64(0x3fe0_001f_a000_0000n),
reinterpretU64AsF64(0x3fe0_0020_8000_0000n),
]; // ~0.5..., due to lack of precision in i16
- const kNegHalfBounds2x16snorm: IntervalBounds = [
+ const kNegHalfEndpoints2x16snorm: IntervalEndpoints = [
reinterpretU64AsF64(0xbfdf_ffc0_6000_0000n),
reinterpretU64AsF64(0xbfdf_ffbf_8000_0000n),
]; // ~-0.5..., due to lack of precision in i16
@@ -5717,13 +5769,13 @@ g.test('unpack2x16floatInterval')
.paramsSubcasesOnly<ScalarToVectorCase>(
// prettier-ignore
[
- { input: 0x00000000, expected: [kZeroBounds, kZeroBounds] },
- { input: 0x00007fff, expected: [kOneBoundsSnorm, kZeroBounds] },
- { input: 0x7fff0000, expected: [kZeroBounds, kOneBoundsSnorm] },
- { input: 0x7fff7fff, expected: [kOneBoundsSnorm, kOneBoundsSnorm] },
- { input: 0x80018001, expected: [kNegOneBoundsSnorm, kNegOneBoundsSnorm] },
- { input: 0x40004000, expected: [kHalfBounds2x16snorm, kHalfBounds2x16snorm] },
- { input: 0xc001c001, expected: [kNegHalfBounds2x16snorm, kNegHalfBounds2x16snorm] },
+ { input: 0x00000000, expected: [kZeroEndpoints, kZeroEndpoints] },
+ { input: 0x00007fff, expected: [kOneEndpointsSnorm, kZeroEndpoints] },
+ { input: 0x7fff0000, expected: [kZeroEndpoints, kOneEndpointsSnorm] },
+ { input: 0x7fff7fff, expected: [kOneEndpointsSnorm, kOneEndpointsSnorm] },
+ { input: 0x80018001, expected: [kNegOneEndpointsSnorm, kNegOneEndpointsSnorm] },
+ { input: 0x40004000, expected: [kHalfEndpoints2x16snorm, kHalfEndpoints2x16snorm] },
+ { input: 0xc001c001, expected: [kNegHalfEndpoints2x16snorm, kNegHalfEndpoints2x16snorm] },
]
)
.fn(t => {
@@ -5740,15 +5792,15 @@ g.test('unpack2x16floatInterval')
// magic numbers that don't pollute the global namespace or have unwieldy long
// names.
{
- const kZeroBounds: IntervalBounds = [
+ const kZeroEndpoints: IntervalEndpoints = [
reinterpretU32AsF32(0x8140_0000),
reinterpretU32AsF32(0x0140_0000),
]; // ~0
- const kOneBounds: IntervalBounds = [
+ const kOneEndpoints: IntervalEndpoints = [
reinterpretU64AsF64(0x3fef_ffff_a000_0000n),
reinterpretU64AsF64(0x3ff0_0000_3000_0000n),
]; // ~1
- const kHalfBounds: IntervalBounds = [
+ const kHalfEndpoints: IntervalEndpoints = [
reinterpretU64AsF64(0x3fe0_000f_a000_0000n),
reinterpretU64AsF64(0x3fe0_0010_8000_0000n),
]; // ~0.5..., due to the lack of accuracy in u16
@@ -5757,11 +5809,11 @@ g.test('unpack2x16floatInterval')
.paramsSubcasesOnly<ScalarToVectorCase>(
// prettier-ignore
[
- { input: 0x00000000, expected: [kZeroBounds, kZeroBounds] },
- { input: 0x0000ffff, expected: [kOneBounds, kZeroBounds] },
- { input: 0xffff0000, expected: [kZeroBounds, kOneBounds] },
- { input: 0xffffffff, expected: [kOneBounds, kOneBounds] },
- { input: 0x80008000, expected: [kHalfBounds, kHalfBounds] },
+ { input: 0x00000000, expected: [kZeroEndpoints, kZeroEndpoints] },
+ { input: 0x0000ffff, expected: [kOneEndpoints, kZeroEndpoints] },
+ { input: 0xffff0000, expected: [kZeroEndpoints, kOneEndpoints] },
+ { input: 0xffffffff, expected: [kOneEndpoints, kOneEndpoints] },
+ { input: 0x80008000, expected: [kHalfEndpoints, kHalfEndpoints] },
]
)
.fn(t => {
@@ -5778,23 +5830,23 @@ g.test('unpack2x16floatInterval')
// magic numbers that don't pollute the global namespace or have unwieldy long
// names.
{
- const kZeroBounds: IntervalBounds = [
+ const kZeroEndpoints: IntervalEndpoints = [
reinterpretU32AsF32(0x8140_0000),
reinterpretU32AsF32(0x0140_0000),
]; // ~0
- const kOneBounds: IntervalBounds = [
+ const kOneEndpoints: IntervalEndpoints = [
reinterpretU64AsF64(0x3fef_ffff_a000_0000n),
reinterpretU64AsF64(0x3ff0_0000_3000_0000n),
]; // ~1
- const kNegOneBounds: IntervalBounds = [
+ const kNegOneEndpoints: IntervalEndpoints = [
reinterpretU64AsF64(0xbff0_0000_3000_0000n),
reinterpretU64AsF64(0xbfef_ffff_a0000_000n),
]; // ~-1
- const kHalfBounds: IntervalBounds = [
+ const kHalfEndpoints: IntervalEndpoints = [
reinterpretU64AsF64(0x3fe0_2040_2000_0000n),
reinterpretU64AsF64(0x3fe0_2041_0000_0000n),
]; // ~0.50196..., due to lack of precision in i8
- const kNegHalfBounds: IntervalBounds = [
+ const kNegHalfEndpoints: IntervalEndpoints = [
reinterpretU64AsF64(0xbfdf_bf7f_6000_0000n),
reinterpretU64AsF64(0xbfdf_bf7e_8000_0000n),
]; // ~-0.49606..., due to lack of precision in i8
@@ -5803,27 +5855,27 @@ g.test('unpack2x16floatInterval')
.paramsSubcasesOnly<ScalarToVectorCase>(
// prettier-ignore
[
- { input: 0x00000000, expected: [kZeroBounds, kZeroBounds, kZeroBounds, kZeroBounds] },
- { input: 0x0000007f, expected: [kOneBounds, kZeroBounds, kZeroBounds, kZeroBounds] },
- { input: 0x00007f00, expected: [kZeroBounds, kOneBounds, kZeroBounds, kZeroBounds] },
- { input: 0x007f0000, expected: [kZeroBounds, kZeroBounds, kOneBounds, kZeroBounds] },
- { input: 0x7f000000, expected: [kZeroBounds, kZeroBounds, kZeroBounds, kOneBounds] },
- { input: 0x00007f7f, expected: [kOneBounds, kOneBounds, kZeroBounds, kZeroBounds] },
- { input: 0x7f7f0000, expected: [kZeroBounds, kZeroBounds, kOneBounds, kOneBounds] },
- { input: 0x7f007f00, expected: [kZeroBounds, kOneBounds, kZeroBounds, kOneBounds] },
- { input: 0x007f007f, expected: [kOneBounds, kZeroBounds, kOneBounds, kZeroBounds] },
- { input: 0x7f7f7f7f, expected: [kOneBounds, kOneBounds, kOneBounds, kOneBounds] },
+ { input: 0x00000000, expected: [kZeroEndpoints, kZeroEndpoints, kZeroEndpoints, kZeroEndpoints] },
+ { input: 0x0000007f, expected: [kOneEndpoints, kZeroEndpoints, kZeroEndpoints, kZeroEndpoints] },
+ { input: 0x00007f00, expected: [kZeroEndpoints, kOneEndpoints, kZeroEndpoints, kZeroEndpoints] },
+ { input: 0x007f0000, expected: [kZeroEndpoints, kZeroEndpoints, kOneEndpoints, kZeroEndpoints] },
+ { input: 0x7f000000, expected: [kZeroEndpoints, kZeroEndpoints, kZeroEndpoints, kOneEndpoints] },
+ { input: 0x00007f7f, expected: [kOneEndpoints, kOneEndpoints, kZeroEndpoints, kZeroEndpoints] },
+ { input: 0x7f7f0000, expected: [kZeroEndpoints, kZeroEndpoints, kOneEndpoints, kOneEndpoints] },
+ { input: 0x7f007f00, expected: [kZeroEndpoints, kOneEndpoints, kZeroEndpoints, kOneEndpoints] },
+ { input: 0x007f007f, expected: [kOneEndpoints, kZeroEndpoints, kOneEndpoints, kZeroEndpoints] },
+ { input: 0x7f7f7f7f, expected: [kOneEndpoints, kOneEndpoints, kOneEndpoints, kOneEndpoints] },
{
input: 0x81818181,
- expected: [kNegOneBounds, kNegOneBounds, kNegOneBounds, kNegOneBounds]
+ expected: [kNegOneEndpoints, kNegOneEndpoints, kNegOneEndpoints, kNegOneEndpoints]
},
{
input: 0x40404040,
- expected: [kHalfBounds, kHalfBounds, kHalfBounds, kHalfBounds]
+ expected: [kHalfEndpoints, kHalfEndpoints, kHalfEndpoints, kHalfEndpoints]
},
{
input: 0xc1c1c1c1,
- expected: [kNegHalfBounds, kNegHalfBounds, kNegHalfBounds, kNegHalfBounds]
+ expected: [kNegHalfEndpoints, kNegHalfEndpoints, kNegHalfEndpoints, kNegHalfEndpoints]
},
]
)
@@ -5841,15 +5893,15 @@ g.test('unpack2x16floatInterval')
// magic numbers that don't pollute the global namespace or have unwieldy long
// names.
{
- const kZeroBounds: IntervalBounds = [
+ const kZeroEndpoints: IntervalEndpoints = [
reinterpretU32AsF32(0x8140_0000),
reinterpretU32AsF32(0x0140_0000),
]; // ~0
- const kOneBounds: IntervalBounds = [
+ const kOneEndpoints: IntervalEndpoints = [
reinterpretU64AsF64(0x3fef_ffff_a000_0000n),
reinterpretU64AsF64(0x3ff0_0000_3000_0000n),
]; // ~1
- const kHalfBounds: IntervalBounds = [
+ const kHalfEndpoints: IntervalEndpoints = [
reinterpretU64AsF64(0x3fe0_100f_a000_0000n),
reinterpretU64AsF64(0x3fe0_1010_8000_0000n),
]; // ~0.50196..., due to lack of precision in u8
@@ -5858,19 +5910,19 @@ g.test('unpack2x16floatInterval')
.paramsSubcasesOnly<ScalarToVectorCase>(
// prettier-ignore
[
- { input: 0x00000000, expected: [kZeroBounds, kZeroBounds, kZeroBounds, kZeroBounds] },
- { input: 0x000000ff, expected: [kOneBounds, kZeroBounds, kZeroBounds, kZeroBounds] },
- { input: 0x0000ff00, expected: [kZeroBounds, kOneBounds, kZeroBounds, kZeroBounds] },
- { input: 0x00ff0000, expected: [kZeroBounds, kZeroBounds, kOneBounds, kZeroBounds] },
- { input: 0xff000000, expected: [kZeroBounds, kZeroBounds, kZeroBounds, kOneBounds] },
- { input: 0x0000ffff, expected: [kOneBounds, kOneBounds, kZeroBounds, kZeroBounds] },
- { input: 0xffff0000, expected: [kZeroBounds, kZeroBounds, kOneBounds, kOneBounds] },
- { input: 0xff00ff00, expected: [kZeroBounds, kOneBounds, kZeroBounds, kOneBounds] },
- { input: 0x00ff00ff, expected: [kOneBounds, kZeroBounds, kOneBounds, kZeroBounds] },
- { input: 0xffffffff, expected: [kOneBounds, kOneBounds, kOneBounds, kOneBounds] },
+ { input: 0x00000000, expected: [kZeroEndpoints, kZeroEndpoints, kZeroEndpoints, kZeroEndpoints] },
+ { input: 0x000000ff, expected: [kOneEndpoints, kZeroEndpoints, kZeroEndpoints, kZeroEndpoints] },
+ { input: 0x0000ff00, expected: [kZeroEndpoints, kOneEndpoints, kZeroEndpoints, kZeroEndpoints] },
+ { input: 0x00ff0000, expected: [kZeroEndpoints, kZeroEndpoints, kOneEndpoints, kZeroEndpoints] },
+ { input: 0xff000000, expected: [kZeroEndpoints, kZeroEndpoints, kZeroEndpoints, kOneEndpoints] },
+ { input: 0x0000ffff, expected: [kOneEndpoints, kOneEndpoints, kZeroEndpoints, kZeroEndpoints] },
+ { input: 0xffff0000, expected: [kZeroEndpoints, kZeroEndpoints, kOneEndpoints, kOneEndpoints] },
+ { input: 0xff00ff00, expected: [kZeroEndpoints, kOneEndpoints, kZeroEndpoints, kOneEndpoints] },
+ { input: 0x00ff00ff, expected: [kOneEndpoints, kZeroEndpoints, kOneEndpoints, kZeroEndpoints] },
+ { input: 0xffffffff, expected: [kOneEndpoints, kOneEndpoints, kOneEndpoints, kOneEndpoints] },
{
input: 0x80808080,
- expected: [kHalfBounds, kHalfBounds, kHalfBounds, kHalfBounds]
+ expected: [kHalfEndpoints, kHalfEndpoints, kHalfEndpoints, kHalfEndpoints]
},
]
)
@@ -5886,7 +5938,7 @@ g.test('unpack2x16floatInterval')
interface VectorToIntervalCase {
input: number[];
- expected: number | IntervalBounds;
+ expected: number | IntervalEndpoints;
}
g.test('lengthIntervalVector')
@@ -5926,10 +5978,10 @@ g.test('lengthIntervalVector')
{input: [-1.0, 1.0, -1.0, 1.0], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0, 1.0, 1.0, 1.0]'] }, // ~2
{input: [0.1, 0.0, 0.0, 0.0], expected: kRootSumSquareExpectionInterval[p.trait]['[0.1]'] }, // ~0.1
- // Test that dot going OOB bounds in the intermediate calculations propagates
- { input: [constants.positive.nearest_max, constants.positive.max, constants.negative.min], expected: kUnboundedBounds },
- { input: [constants.positive.max, constants.positive.nearest_max, constants.negative.min], expected: kUnboundedBounds },
- { input: [constants.negative.min, constants.positive.max, constants.positive.nearest_max], expected: kUnboundedBounds },
+ // Test that dot going OOB in the intermediate calculations propagates
+ { input: [constants.positive.nearest_max, constants.positive.max, constants.negative.min], expected: kUnboundedEndpoints },
+ { input: [constants.positive.max, constants.positive.nearest_max, constants.negative.min], expected: kUnboundedEndpoints },
+ { input: [constants.negative.min, constants.positive.max, constants.positive.nearest_max], expected: kUnboundedEndpoints },
];
})
)
@@ -5945,7 +5997,7 @@ g.test('lengthIntervalVector')
interface VectorPairToIntervalCase {
input: [number[], number[]];
- expected: number | IntervalBounds;
+ expected: number | IntervalEndpoints;
}
g.test('distanceIntervalVector')
@@ -5956,11 +6008,11 @@ g.test('distanceIntervalVector')
.expandWithParams<VectorPairToIntervalCase>(p => {
// prettier-ignore
return [
- // distance(x, y), where x - y = 0 has an acceptance interval of kUnboundedBounds,
- // because distance(x, y) = length(x - y), and length(0) = kUnboundedBounds.
+ // distance(x, y), where x - y = 0 has an acceptance interval of kUnboundedEndpoints,
+ // because distance(x, y) = length(x - y), and length(0) = kUnboundedEndpoints.
// vec2
- { input: [[1.0, 0.0], [1.0, 0.0]], expected: kUnboundedBounds },
+ { input: [[1.0, 0.0], [1.0, 0.0]], expected: kUnboundedEndpoints },
{ input: [[1.0, 0.0], [0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0]'] }, // ~1
{ input: [[0.0, 0.0], [1.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0]'] }, // ~1
{ input: [[-1.0, 0.0], [0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0]'] }, // ~1
@@ -5969,7 +6021,7 @@ g.test('distanceIntervalVector')
{ input: [[0.1, 0.0], [0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[0.1]'] }, // ~0.1
// vec3
- { input: [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: kUnboundedBounds },
+ { input: [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: kUnboundedEndpoints },
{ input: [[1.0, 0.0, 0.0], [0.0, 0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0]'] }, // ~1
{ input: [[0.0, 1.0, 0.0], [0.0, 0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0]'] }, // ~1
{ input: [[0.0, 0.0, 1.0], [0.0, 0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0]'] }, // ~1
@@ -5984,7 +6036,7 @@ g.test('distanceIntervalVector')
{ input: [[0.0, 0.0, 0.0], [0.1, 0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[0.1]'] }, // ~0.1
// vec4
- { input: [[1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: kUnboundedBounds },
+ { input: [[1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: kUnboundedEndpoints },
{ input: [[1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0]'] }, // ~1
{ input: [[0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0]'] }, // ~1
{ input: [[0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 0.0]], expected: kRootSumSquareExpectionInterval[p.trait]['[1.0]'] }, // ~1
@@ -6019,7 +6071,7 @@ const kDotIntervalCases = {
// 3.0*3.0 = 9.0 is much smaller than kValue.f32.positive.max, as a result
// kValue.f32.positive.max + 9.0 = kValue.f32.positive.max in f32 and even f64. So, if the
// positive and negative large number cancel each other first, the result would be
- // 2.0*2.0+3.0*3.0 = 13. Otherwise, the resule would be 0.0 or 4.0 or 9.0.
+ // 2.0*2.0+3.0*3.0 = 13. Otherwise, the result would be 0.0 or 4.0 or 9.0.
// https://github.com/gpuweb/cts/issues/2155
{ input: [[kValue.f32.positive.max, 1.0, 2.0, 3.0], [-1.0, kValue.f32.positive.max, -2.0, -3.0]], expected: [-13, 0] },
{ input: [[kValue.f32.positive.max, 1.0, 2.0, 3.0], [1.0, kValue.f32.negative.min, 2.0, 3.0]], expected: [0, 13] },
@@ -6029,10 +6081,10 @@ const kDotIntervalCases = {
// 3.0*3.0 = 9.0 is not small enough comparing to kValue.f16.positive.max = 65504, as a result
// kValue.f16.positive.max + 9.0 = 65513 is exactly representable in f32 and f64. So, if the
// positive and negative large number don't cancel each other first, the computation will
- // overflow f16 and result in unbounded bounds.
+ // overflow f16 and result in unbounded endpoints.
// https://github.com/gpuweb/cts/issues/2155
- { input: [[kValue.f16.positive.max, 1.0, 2.0, 3.0], [-1.0, kValue.f16.positive.max, -2.0, -3.0]], expected: kUnboundedBounds },
- { input: [[kValue.f16.positive.max, 1.0, 2.0, 3.0], [1.0, kValue.f16.negative.min, 2.0, 3.0]], expected: kUnboundedBounds },
+ { input: [[kValue.f16.positive.max, 1.0, 2.0, 3.0], [-1.0, kValue.f16.positive.max, -2.0, -3.0]], expected: kUnboundedEndpoints },
+ { input: [[kValue.f16.positive.max, 1.0, 2.0, 3.0], [1.0, kValue.f16.negative.min, 2.0, 3.0]], expected: kUnboundedEndpoints },
] as VectorPairToIntervalCase[],
} as const;
@@ -6052,7 +6104,7 @@ g.test('dotInterval')
{ input: [[1.0, 1.0], [1.0, 1.0]], expected: 2.0 },
{ input: [[-1.0, -1.0], [-1.0, -1.0]], expected: 2.0 },
{ input: [[-1.0, 1.0], [1.0, -1.0]], expected: -2.0 },
- { input: [[0.1, 0.0], [1.0, 0.0]], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1']}, // correclt rounded of 0.1
+ { input: [[0.1, 0.0], [1.0, 0.0]], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1']}, // correctly rounded of 0.1
// vec3
{ input: [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: 1.0 },
@@ -6061,7 +6113,7 @@ g.test('dotInterval')
{ input: [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], expected: 3.0 },
{ input: [[-1.0, -1.0, -1.0], [-1.0, -1.0, -1.0]], expected: 3.0 },
{ input: [[1.0, -1.0, -1.0], [-1.0, 1.0, -1.0]], expected: -1.0 },
- { input: [[0.1, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1']}, // correclt rounded of 0.1
+ { input: [[0.1, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: kConstantCorrectlyRoundedExpectation[p.trait]['0.1']}, // correctly rounded of 0.1
// vec4
{ input: [[1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: 1.0 },
@@ -6076,12 +6128,12 @@ g.test('dotInterval')
...kDotIntervalCases[p.trait],
// Test that going out of bounds in the intermediate calculations is caught correctly.
- { input: [[constants.positive.nearest_max, constants.positive.max, constants.negative.min], [1.0, 1.0, 1.0]], expected: kUnboundedBounds },
- { input: [[constants.positive.nearest_max, constants.negative.min, constants.positive.max], [1.0, 1.0, 1.0]], expected: kUnboundedBounds },
- { input: [[constants.positive.max, constants.positive.nearest_max, constants.negative.min], [1.0, 1.0, 1.0]], expected: kUnboundedBounds },
- { input: [[constants.negative.min, constants.positive.nearest_max, constants.positive.max], [1.0, 1.0, 1.0]], expected: kUnboundedBounds },
- { input: [[constants.positive.max, constants.negative.min, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: kUnboundedBounds },
- { input: [[constants.negative.min, constants.positive.max, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: kUnboundedBounds },
+ { input: [[constants.positive.nearest_max, constants.positive.max, constants.negative.min], [1.0, 1.0, 1.0]], expected: kUnboundedEndpoints },
+ { input: [[constants.positive.nearest_max, constants.negative.min, constants.positive.max], [1.0, 1.0, 1.0]], expected: kUnboundedEndpoints },
+ { input: [[constants.positive.max, constants.positive.nearest_max, constants.negative.min], [1.0, 1.0, 1.0]], expected: kUnboundedEndpoints },
+ { input: [[constants.negative.min, constants.positive.nearest_max, constants.positive.max], [1.0, 1.0, 1.0]], expected: kUnboundedEndpoints },
+ { input: [[constants.positive.max, constants.negative.min, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: kUnboundedEndpoints },
+ { input: [[constants.negative.min, constants.positive.max, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: kUnboundedEndpoints },
];
})
)
@@ -6098,54 +6150,54 @@ g.test('dotInterval')
interface VectorToVectorCase {
input: number[];
- expected: (number | IntervalBounds)[];
+ expected: (number | IntervalEndpoints)[];
}
// prettier-ignore
const kNormalizeIntervalCases = {
f32: [
// vec2
- {input: [1.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0]
- {input: [0.0, 1.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)]] }, // [ ~0.0, ~1.0]
- {input: [-1.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_0000_b000_0000n), reinterpretU64AsF64(0xbfef_fffe_7000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0]
- {input: [1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe6_a09d_5000_0000n), reinterpretU64AsF64(0x3fe6_a09f_9000_0000n)], [reinterpretU64AsF64(0x3fe6_a09d_5000_0000n), reinterpretU64AsF64(0x3fe6_a09f_9000_0000n)]] }, // [ ~1/√2, ~1/√2]
+ { input: [1.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0]
+ { input: [0.0, 1.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)]] }, // [ ~0.0, ~1.0]
+ { input: [-1.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_0000_b000_0000n), reinterpretU64AsF64(0xbfef_fffe_7000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0]
+ { input: [1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe6_a09d_5000_0000n), reinterpretU64AsF64(0x3fe6_a09f_9000_0000n)], [reinterpretU64AsF64(0x3fe6_a09d_5000_0000n), reinterpretU64AsF64(0x3fe6_a09f_9000_0000n)]] }, // [ ~1/√2, ~1/√2]
// vec3
- {input: [1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0]
- {input: [0.0, 1.0, 0.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~0.0, ~1.0, ~0.0]
- {input: [0.0, 0.0, 1.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)]] }, // [ ~0.0, ~0.0, ~1.0]
- {input: [-1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_0000_b000_0000n), reinterpretU64AsF64(0xbfef_fffe_7000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0]
- {input: [1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe2_79a6_5000_0000n), reinterpretU64AsF64(0x3fe2_79a8_5000_0000n)], [reinterpretU64AsF64(0x3fe2_79a6_5000_0000n), reinterpretU64AsF64(0x3fe2_79a8_5000_0000n)], [reinterpretU64AsF64(0x3fe2_79a6_5000_0000n), reinterpretU64AsF64(0x3fe2_79a8_5000_0000n)]] }, // [ ~1/√3, ~1/√3, ~1/√3]
+ { input: [1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0]
+ { input: [0.0, 1.0, 0.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~0.0, ~1.0, ~0.0]
+ { input: [0.0, 0.0, 1.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)]] }, // [ ~0.0, ~0.0, ~1.0]
+ { input: [-1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_0000_b000_0000n), reinterpretU64AsF64(0xbfef_fffe_7000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0]
+ { input: [1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe2_79a6_5000_0000n), reinterpretU64AsF64(0x3fe2_79a8_5000_0000n)], [reinterpretU64AsF64(0x3fe2_79a6_5000_0000n), reinterpretU64AsF64(0x3fe2_79a8_5000_0000n)], [reinterpretU64AsF64(0x3fe2_79a6_5000_0000n), reinterpretU64AsF64(0x3fe2_79a8_5000_0000n)]] }, // [ ~1/√3, ~1/√3, ~1/√3]
// vec4
- {input: [1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0, ~0.0]
- {input: [0.0, 1.0, 0.0, 0.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~0.0, ~1.0, ~0.0, ~0.0]
- {input: [0.0, 0.0, 1.0, 0.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~0.0, ~0.0, ~1.0, ~0.0]
- {input: [0.0, 0.0, 0.0, 1.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)]] }, // [ ~0.0, ~0.0, ~0.0, ~1.0]
- {input: [-1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_0000_b000_0000n), reinterpretU64AsF64(0xbfef_fffe_7000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0, ~0.0]
- {input: [1.0, 1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)], [reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)], [reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)], [reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)]] }, // [ ~1/√4, ~1/√4, ~1/√4]
+ { input: [1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0, ~0.0]
+ { input: [0.0, 1.0, 0.0, 0.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~0.0, ~1.0, ~0.0, ~0.0]
+ { input: [0.0, 0.0, 1.0, 0.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~0.0, ~0.0, ~1.0, ~0.0]
+ { input: [0.0, 0.0, 0.0, 1.0], expected: [[reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU64AsF64(0x3fef_fffe_7000_0000n), reinterpretU64AsF64(0x3ff0_0000_b000_0000n)]] }, // [ ~0.0, ~0.0, ~0.0, ~1.0]
+ { input: [-1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_0000_b000_0000n), reinterpretU64AsF64(0xbfef_fffe_7000_0000n)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)], [reinterpretU32AsF32(0x81200000), reinterpretU32AsF32(0x01200000)]] }, // [ ~1.0, ~0.0, ~0.0, ~0.0]
+ { input: [1.0, 1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)], [reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)], [reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)], [reinterpretU64AsF64(0x3fdf_fffe_7000_0000n), reinterpretU64AsF64(0x3fe0_0000_b000_0000n)]] }, // [ ~1/√4, ~1/√4, ~1/√4]
] as VectorToVectorCase[],
f16: [
// vec2
- {input: [1.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0]
- {input: [0.0, 1.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)]] }, // [ ~0.0, ~1.0]
- {input: [-1.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_1600_0000_0000n), reinterpretU64AsF64(0xbfef_ce00_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0]
- {input: [1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe6_7e00_0000_0000n), reinterpretU64AsF64(0x3fe6_c600_0000_0000n)], [reinterpretU64AsF64(0x3fe6_7e00_0000_0000n), reinterpretU64AsF64(0x3fe6_c600_0000_0000n)]] }, // [ ~1/√2, ~1/√2]
+ { input: [1.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0]
+ { input: [0.0, 1.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)]] }, // [ ~0.0, ~1.0]
+ { input: [-1.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_1600_0000_0000n), reinterpretU64AsF64(0xbfef_ce00_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0]
+ { input: [1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe6_7e00_0000_0000n), reinterpretU64AsF64(0x3fe6_c600_0000_0000n)], [reinterpretU64AsF64(0x3fe6_7e00_0000_0000n), reinterpretU64AsF64(0x3fe6_c600_0000_0000n)]] }, // [ ~1/√2, ~1/√2]
// vec3
- {input: [1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0, ~0.0]
- {input: [0.0, 1.0, 0.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~0.0, ~1.0, ~0.0]
- {input: [0.0, 0.0, 1.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)]] }, // [ ~0.0, ~0.0, ~1.0]
- {input: [-1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_1600_0000_0000n), reinterpretU64AsF64(0xbfef_ce00_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0, ~0.0]
- {input: [1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe2_5a00_0000_0000n), reinterpretU64AsF64(0x3fe2_9a00_0000_0000n)], [reinterpretU64AsF64(0x3fe2_5a00_0000_0000n), reinterpretU64AsF64(0x3fe2_9a00_0000_0000n)], [reinterpretU64AsF64(0x3fe2_5a00_0000_0000n), reinterpretU64AsF64(0x3fe2_9a00_0000_0000n)]] }, // [ ~1/√3, ~1/√3, ~1/√3]
+ { input: [1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0, ~0.0]
+ { input: [0.0, 1.0, 0.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~0.0, ~1.0, ~0.0]
+ { input: [0.0, 0.0, 1.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)]] }, // [ ~0.0, ~0.0, ~1.0]
+ { input: [-1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_1600_0000_0000n), reinterpretU64AsF64(0xbfef_ce00_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0, ~0.0]
+ { input: [1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fe2_5a00_0000_0000n), reinterpretU64AsF64(0x3fe2_9a00_0000_0000n)], [reinterpretU64AsF64(0x3fe2_5a00_0000_0000n), reinterpretU64AsF64(0x3fe2_9a00_0000_0000n)], [reinterpretU64AsF64(0x3fe2_5a00_0000_0000n), reinterpretU64AsF64(0x3fe2_9a00_0000_0000n)]] }, // [ ~1/√3, ~1/√3, ~1/√3]
// vec4
- {input: [1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0, ~0.0, ~0.0]
- {input: [0.0, 1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~0.0, ~1.0, ~0.0, ~0.0]
- {input: [0.0, 0.0, 1.0, 0.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~0.0, ~0.0, ~1.0, ~0.0]
- {input: [0.0, 0.0, 0.0, 1.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)]] }, // [ ~0.0, ~0.0, ~0.0, ~1.0]
- {input: [-1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_1600_0000_0000n), reinterpretU64AsF64(0xbfef_ce00_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0, ~0.0, ~0.0]
- {input: [1.0, 1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)], [reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)], [reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)], [reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)]] }, // [ ~1/√4, ~1/√4, ~1/√4]
+ { input: [1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0, ~0.0, ~0.0]
+ { input: [0.0, 1.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~0.0, ~1.0, ~0.0, ~0.0]
+ { input: [0.0, 0.0, 1.0, 0.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~0.0, ~0.0, ~1.0, ~0.0]
+ { input: [0.0, 0.0, 0.0, 1.0], expected: [[reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0x3fef_ce00_0000_0000n), reinterpretU64AsF64(0x3ff0_1600_0000_0000n)]] }, // [ ~0.0, ~0.0, ~0.0, ~1.0]
+ { input: [-1.0, 0.0, 0.0, 0.0], expected: [[reinterpretU64AsF64(0xbff0_1600_0000_0000n), reinterpretU64AsF64(0xbfef_ce00_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)], [reinterpretU64AsF64(0xbf24_0000_0000_0000n), reinterpretU64AsF64(0x3f24_0000_0000_0000n)]] }, // [ ~1.0, ~0.0, ~0.0, ~0.0]
+ { input: [1.0, 1.0, 1.0, 1.0], expected: [[reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)], [reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)], [reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)], [reinterpretU64AsF64(0x3fdf_ce00_0000_0000n), reinterpretU64AsF64(0x3fe0_1600_0000_0000n)]] }, // [ ~1/√4, ~1/√4, ~1/√4]
] as VectorToVectorCase[],
} as const;
@@ -6154,7 +6206,20 @@ g.test('normalizeInterval')
u
.combine('trait', ['f32', 'f16'] as const)
.beginSubcases()
- .expandWithParams<VectorToVectorCase>(p => kNormalizeIntervalCases[p.trait])
+ .expandWithParams<VectorToVectorCase>(p => {
+ const trait = FP[p.trait];
+ const constants = trait.constants();
+ // prettier-ignore
+ return [
+ ...kNormalizeIntervalCases[p.trait],
+
+ // Very small vectors go OOB due to division
+ { input: [constants.positive.subnormal.max, constants.positive.subnormal.max], expected: [kUnboundedEndpoints, kUnboundedEndpoints], },
+
+ // Very large vectors go OOB due to overflow
+ { input: [constants.positive.max, constants.positive.max], expected: [kUnboundedEndpoints, kUnboundedEndpoints], },
+ ];
+ })
)
.fn(t => {
const x = t.params.input;
@@ -6169,7 +6234,7 @@ g.test('normalizeInterval')
interface VectorPairToVectorCase {
input: [number[], number[]];
- expected: (number | IntervalBounds)[];
+ expected: (number | IntervalEndpoints)[];
}
// prettier-ignore
@@ -6218,30 +6283,12 @@ const kCrossIntervalCases = {
]
},
] as VectorPairToVectorCase[],
- abstract: [
- { input: [
- [kValue.f64.positive.subnormal.max, kValue.f64.negative.subnormal.max, kValue.f64.negative.subnormal.min],
- [kValue.f64.negative.subnormal.min, kValue.f64.positive.subnormal.min, kValue.f64.negative.subnormal.max]
- ],
- expected: [0.0, 0.0, 0.0]
- },
- { input: [
- [0.1, -0.1, -0.1],
- [-0.1, 0.1, -0.1]
- ],
- expected: [
- reinterpretU64AsF64(0x3f94_7ae1_47ae_147cn), // ~0.02
- reinterpretU64AsF64(0x3f94_7ae1_47ae_147cn), // ~0.02
- 0.0
- ]
- },
- ] as VectorPairToVectorCase[],
} as const;
g.test('crossInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16', 'abstract'] as const)
+ .combine('trait', ['f32', 'f16'] as const)
.beginSubcases()
.expandWithParams<VectorPairToVectorCase>(p => {
const trait = FP[p.trait];
@@ -6261,6 +6308,9 @@ g.test('crossInterval')
{ input: [[1.0, -1.0, -1.0], [-1.0, 1.0, -1.0]], expected: [2.0, 2.0, 0.0] },
{ input: [[1.0, 2, 3], [1.0, 5.0, 7.0]], expected: [-1, -4, 3] },
...kCrossIntervalCases[p.trait],
+
+ // OOB
+ { input: [[constants.positive.max, 1.0, 1.0], [1.0, constants.positive.max, -1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
];
})
)
@@ -6320,6 +6370,7 @@ g.test('reflectInterval')
{ input: [[0.0, 1.0], [1.0, 0.0]], expected: [0.0, 1.0] },
{ input: [[1.0, 1.0], [1.0, 1.0]], expected: [-3.0, -3.0] },
{ input: [[-1.0, -1.0], [1.0, 1.0]], expected: [3.0, 3.0] },
+
// vec3s
{ input: [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0]], expected: [-1.0, 0.0, 0.0] },
{ input: [[0.0, 1.0, 0.0], [1.0, 0.0, 0.0]], expected: [0.0, 1.0, 0.0] },
@@ -6328,6 +6379,7 @@ g.test('reflectInterval')
{ input: [[1.0, 0.0, 0.0], [0.0, 0.0, 1.0]], expected: [1.0, 0.0, 0.0] },
{ input: [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], expected: [-5.0, -5.0, -5.0] },
{ input: [[-1.0, -1.0, -1.0], [1.0, 1.0, 1.0]], expected: [5.0, 5.0, 5.0] },
+
// vec4s
{ input: [[1.0, 0.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: [-1.0, 0.0, 0.0, 0.0] },
{ input: [[0.0, 1.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0]], expected: [0.0, 1.0, 0.0, 0.0] },
@@ -6337,16 +6389,17 @@ g.test('reflectInterval')
{ input: [[1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0]], expected: [1.0, 0.0, 0.0, 0.0] },
{ input: [[1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0]], expected: [1.0, 0.0, 0.0, 0.0] },
{ input: [[-1.0, -1.0, -1.0, -1.0], [1.0, 1.0, 1.0, 1.0]], expected: [7.0, 7.0, 7.0, 7.0] },
- // Test that dot going OOB bounds in the intermediate calculations propagates
- { input: [[constants.positive.nearest_max, constants.positive.max, constants.negative.min], [1.0, 1.0, 1.0]], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.positive.nearest_max, constants.negative.min, constants.positive.max], [1.0, 1.0, 1.0]], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.positive.max, constants.positive.nearest_max, constants.negative.min], [1.0, 1.0, 1.0]], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.negative.min, constants.positive.nearest_max, constants.positive.max], [1.0, 1.0, 1.0]], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.positive.max, constants.negative.min, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.negative.min, constants.positive.max, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
+
+ // Test that dot going OOB in the intermediate calculations propagates
+ { input: [[constants.positive.nearest_max, constants.positive.max, constants.negative.min], [1.0, 1.0, 1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.positive.nearest_max, constants.negative.min, constants.positive.max], [1.0, 1.0, 1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.positive.max, constants.positive.nearest_max, constants.negative.min], [1.0, 1.0, 1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.negative.min, constants.positive.nearest_max, constants.positive.max], [1.0, 1.0, 1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.positive.max, constants.negative.min, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.negative.min, constants.positive.max, constants.positive.nearest_max], [1.0, 1.0, 1.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
// Test that post-dot going OOB propagates
- { input: [[constants.positive.max, 1.0, 2.0, 3.0], [-1.0, constants.positive.max, -2.0, -3.0]], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
+ { input: [[constants.positive.max, 1.0, 2.0, 3.0], [-1.0, constants.positive.max, -2.0, -3.0]], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
];
})
)
@@ -6365,7 +6418,7 @@ g.test('reflectInterval')
interface MatrixToScalarCase {
input: number[][];
- expected: number | IntervalBounds;
+ expected: number | IntervalEndpoints;
}
g.test('determinantInterval')
@@ -6480,7 +6533,7 @@ g.test('determinantInterval')
interface MatrixToMatrixCase {
input: number[][];
- expected: (number | IntervalBounds)[][];
+ expected: (number | IntervalEndpoints)[][];
}
g.test('transposeInterval')
@@ -6634,7 +6687,7 @@ g.test('transposeInterval')
interface MatrixPairToMatrixCase {
input: [number[][], number[][]];
- expected: (number | IntervalBounds)[][];
+ expected: (number | IntervalEndpoints)[][];
}
g.test('additionMatrixMatrixInterval')
@@ -6642,184 +6695,205 @@ g.test('additionMatrixMatrixInterval')
u
.combine('trait', ['f32', 'f16', 'abstract'] as const)
.beginSubcases()
- .combineWithParams<MatrixPairToMatrixCase>([
- // 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],
+ .expandWithParams<MatrixPairToMatrixCase>(p => {
+ const trait = FP[p.trait];
+ const constants = trait.constants();
+ return [
+ // 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],
+ ],
],
- [
- [10, 20],
- [30, 40],
+ expected: [
+ [11, 22],
+ [33, 44],
],
- ],
- expected: [
- [11, 22],
- [33, 44],
- ],
- },
- {
- input: [
- [
- [1, 2],
- [3, 4],
- [5, 6],
+ },
+ {
+ input: [
+ [
+ [1, 2],
+ [3, 4],
+ [5, 6],
+ ],
+ [
+ [10, 20],
+ [30, 40],
+ [50, 60],
+ ],
],
- [
- [10, 20],
- [30, 40],
- [50, 60],
+ expected: [
+ [11, 22],
+ [33, 44],
+ [55, 66],
],
- ],
- expected: [
- [11, 22],
- [33, 44],
- [55, 66],
- ],
- },
- {
- input: [
- [
- [1, 2],
- [3, 4],
- [5, 6],
- [7, 8],
+ },
+ {
+ input: [
+ [
+ [1, 2],
+ [3, 4],
+ [5, 6],
+ [7, 8],
+ ],
+ [
+ [10, 20],
+ [30, 40],
+ [50, 60],
+ [70, 80],
+ ],
],
- [
- [10, 20],
- [30, 40],
- [50, 60],
- [70, 80],
+ expected: [
+ [11, 22],
+ [33, 44],
+ [55, 66],
+ [77, 88],
],
- ],
- expected: [
- [11, 22],
- [33, 44],
- [55, 66],
- [77, 88],
- ],
- },
- {
- input: [
- [
- [1, 2, 3],
- [4, 5, 6],
+ },
+ {
+ input: [
+ [
+ [1, 2, 3],
+ [4, 5, 6],
+ ],
+ [
+ [10, 20, 30],
+ [40, 50, 60],
+ ],
],
- [
- [10, 20, 30],
- [40, 50, 60],
+ expected: [
+ [11, 22, 33],
+ [44, 55, 66],
],
- ],
- expected: [
- [11, 22, 33],
- [44, 55, 66],
- ],
- },
- {
- input: [
- [
- [1, 2, 3],
- [4, 5, 6],
- [7, 8, 9],
+ },
+ {
+ input: [
+ [
+ [1, 2, 3],
+ [4, 5, 6],
+ [7, 8, 9],
+ ],
+ [
+ [10, 20, 30],
+ [40, 50, 60],
+ [70, 80, 90],
+ ],
],
- [
- [10, 20, 30],
- [40, 50, 60],
- [70, 80, 90],
+ expected: [
+ [11, 22, 33],
+ [44, 55, 66],
+ [77, 88, 99],
],
- ],
- expected: [
- [11, 22, 33],
- [44, 55, 66],
- [77, 88, 99],
- ],
- },
- {
- input: [
- [
- [1, 2, 3],
- [4, 5, 6],
- [7, 8, 9],
- [10, 11, 12],
+ },
+ {
+ 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],
+ ],
],
- [
- [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],
],
- ],
- expected: [
- [11, 22, 33],
- [44, 55, 66],
- [77, 88, 99],
- [1010, 1111, 1212],
- ],
- },
- {
- input: [
- [
- [1, 2, 3, 4],
- [5, 6, 7, 8],
+ },
+ {
+ input: [
+ [
+ [1, 2, 3, 4],
+ [5, 6, 7, 8],
+ ],
+ [
+ [10, 20, 30, 40],
+ [50, 60, 70, 80],
+ ],
],
- [
- [10, 20, 30, 40],
- [50, 60, 70, 80],
+ expected: [
+ [11, 22, 33, 44],
+ [55, 66, 77, 88],
],
- ],
- expected: [
- [11, 22, 33, 44],
- [55, 66, 77, 88],
- ],
- },
- {
- input: [
- [
- [1, 2, 3, 4],
- [5, 6, 7, 8],
- [9, 10, 11, 12],
+ },
+ {
+ 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],
+ ],
],
- [
- [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],
],
- ],
- 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],
+ },
+ {
+ 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],
+ ],
],
- [
- [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],
],
- ],
- expected: [
- [11, 22, 33, 44],
- [55, 66, 77, 88],
- [99, 1010, 1111, 1212],
- [1313, 1414, 1515, 1616],
- ],
- },
- ])
+ },
+ // Test the OOB is handled component-wise
+ {
+ input: [
+ [
+ [constants.positive.max, 2],
+ [3, 4],
+ ],
+ [
+ [constants.positive.max, 20],
+ [30, 40],
+ ],
+ ],
+ expected: [
+ [kUnboundedEndpoints, 22],
+ [33, 44],
+ ],
+ },
+ ];
+ })
)
.fn(t => {
const [x, y] = t.params.input;
@@ -6839,184 +6913,205 @@ g.test('subtractionMatrixMatrixInterval')
u
.combine('trait', ['f32', 'f16', 'abstract'] as const)
.beginSubcases()
- .combineWithParams<MatrixPairToMatrixCase>([
- // 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.
- {
- input: [
- [
- [1, 2],
- [3, 4],
+ .expandWithParams<MatrixPairToMatrixCase>(p => {
+ const trait = FP[p.trait];
+ const constants = trait.constants();
+ return [
+ // 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.
+ {
+ input: [
+ [
+ [1, 2],
+ [3, 4],
+ ],
+ [
+ [-10, -20],
+ [-30, -40],
+ ],
],
- [
- [-10, -20],
- [-30, -40],
+ expected: [
+ [11, 22],
+ [33, 44],
],
- ],
- expected: [
- [11, 22],
- [33, 44],
- ],
- },
- {
- input: [
- [
- [1, 2],
- [3, 4],
- [5, 6],
+ },
+ {
+ input: [
+ [
+ [1, 2],
+ [3, 4],
+ [5, 6],
+ ],
+ [
+ [-10, -20],
+ [-30, -40],
+ [-50, -60],
+ ],
],
- [
- [-10, -20],
- [-30, -40],
- [-50, -60],
+ expected: [
+ [11, 22],
+ [33, 44],
+ [55, 66],
],
- ],
- expected: [
- [11, 22],
- [33, 44],
- [55, 66],
- ],
- },
- {
- input: [
- [
- [1, 2],
- [3, 4],
- [5, 6],
- [7, 8],
+ },
+ {
+ input: [
+ [
+ [1, 2],
+ [3, 4],
+ [5, 6],
+ [7, 8],
+ ],
+ [
+ [-10, -20],
+ [-30, -40],
+ [-50, -60],
+ [-70, -80],
+ ],
],
- [
- [-10, -20],
- [-30, -40],
- [-50, -60],
- [-70, -80],
+ expected: [
+ [11, 22],
+ [33, 44],
+ [55, 66],
+ [77, 88],
],
- ],
- expected: [
- [11, 22],
- [33, 44],
- [55, 66],
- [77, 88],
- ],
- },
- {
- input: [
- [
- [1, 2, 3],
- [4, 5, 6],
+ },
+ {
+ input: [
+ [
+ [1, 2, 3],
+ [4, 5, 6],
+ ],
+ [
+ [-10, -20, -30],
+ [-40, -50, -60],
+ ],
],
- [
- [-10, -20, -30],
- [-40, -50, -60],
+ expected: [
+ [11, 22, 33],
+ [44, 55, 66],
],
- ],
- expected: [
- [11, 22, 33],
- [44, 55, 66],
- ],
- },
- {
- input: [
- [
- [1, 2, 3],
- [4, 5, 6],
- [7, 8, 9],
+ },
+ {
+ input: [
+ [
+ [1, 2, 3],
+ [4, 5, 6],
+ [7, 8, 9],
+ ],
+ [
+ [-10, -20, -30],
+ [-40, -50, -60],
+ [-70, -80, -90],
+ ],
],
- [
- [-10, -20, -30],
- [-40, -50, -60],
- [-70, -80, -90],
+ expected: [
+ [11, 22, 33],
+ [44, 55, 66],
+ [77, 88, 99],
],
- ],
- expected: [
- [11, 22, 33],
- [44, 55, 66],
- [77, 88, 99],
- ],
- },
- {
- input: [
- [
- [1, 2, 3],
- [4, 5, 6],
- [7, 8, 9],
- [10, 11, 12],
+ },
+ {
+ 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],
+ ],
],
- [
- [-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],
],
- ],
- expected: [
- [11, 22, 33],
- [44, 55, 66],
- [77, 88, 99],
- [1010, 1111, 1212],
- ],
- },
- {
- input: [
- [
- [1, 2, 3, 4],
- [5, 6, 7, 8],
+ },
+ {
+ input: [
+ [
+ [1, 2, 3, 4],
+ [5, 6, 7, 8],
+ ],
+ [
+ [-10, -20, -30, -40],
+ [-50, -60, -70, -80],
+ ],
],
- [
- [-10, -20, -30, -40],
- [-50, -60, -70, -80],
+ expected: [
+ [11, 22, 33, 44],
+ [55, 66, 77, 88],
],
- ],
- expected: [
- [11, 22, 33, 44],
- [55, 66, 77, 88],
- ],
- },
- {
- input: [
- [
- [1, 2, 3, 4],
- [5, 6, 7, 8],
- [9, 10, 11, 12],
+ },
+ {
+ 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],
+ ],
],
- [
- [-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],
],
- ],
- 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],
+ },
+ {
+ 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],
+ ],
],
- [
- [-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],
],
- ],
- expected: [
- [11, 22, 33, 44],
- [55, 66, 77, 88],
- [99, 1010, 1111, 1212],
- [1313, 1414, 1515, 1616],
- ],
- },
- ])
+ },
+ // Test the OOB is handled component-wise
+ {
+ input: [
+ [
+ [constants.positive.max, 2],
+ [3, 4],
+ ],
+ [
+ [constants.negative.min, -20],
+ [-30, -40],
+ ],
+ ],
+ expected: [
+ [kUnboundedEndpoints, 22],
+ [33, 44],
+ ],
+ },
+ ];
+ })
)
.fn(t => {
const [x, y] = t.params.input;
@@ -7577,7 +7672,7 @@ g.test('multiplicationMatrixMatrixInterval')
interface MatrixScalarToMatrixCase {
matrix: number[][];
scalar: number;
- expected: (number | IntervalBounds)[][];
+ expected: (number | IntervalEndpoints)[][];
}
const kMultiplicationMatrixScalarIntervalCases = {
@@ -7609,14 +7704,30 @@ const kMultiplicationMatrixScalarIntervalCases = {
],
},
] as MatrixScalarToMatrixCase[],
+ abstract: [
+ // From https://github.com/gpuweb/cts/issues/3044
+ {
+ matrix: [
+ [kValue.f64.negative.min, 0],
+ [0, 0],
+ ],
+ scalar: kValue.f64.negative.subnormal.min,
+ expected: [
+ [[0, reinterpretU64AsF64(0x400ffffffffffffdn)], 0], // [[0, 3.9999995...], 0],
+ [0, 0],
+ ],
+ },
+ ] as MatrixScalarToMatrixCase[],
} as const;
g.test('multiplicationMatrixScalarInterval')
.params(u =>
u
- .combine('trait', ['f32', 'f16'] as const)
+ .combine('trait', ['f32', 'f16', 'abstract'] as const)
.beginSubcases()
.expandWithParams<MatrixScalarToMatrixCase>(p => {
+ const trait = FP[p.trait];
+ const constants = trait.constants();
// Primarily testing that different shapes of matrices are handled correctly
// here, to reduce test duplication. Additional testing for edge case
// discovered in https://github.com/gpuweb/cts/issues/3044.
@@ -7743,6 +7854,18 @@ g.test('multiplicationMatrixScalarInterval')
],
},
...kMultiplicationMatrixScalarIntervalCases[p.trait],
+ // Test that OOB is component-wise
+ {
+ matrix: [
+ [1, 2],
+ [constants.positive.max, 4],
+ ],
+ scalar: 10,
+ expected: [
+ [10, 20],
+ [kUnboundedEndpoints, 40],
+ ],
+ },
];
})
)
@@ -7766,7 +7889,7 @@ g.test('multiplicationMatrixScalarInterval')
interface MatrixVectorToVectorCase {
matrix: number[][];
vector: number[];
- expected: (number | IntervalBounds)[];
+ expected: (number | IntervalEndpoints)[];
}
g.test('multiplicationMatrixVectorInterval')
@@ -7883,7 +8006,7 @@ g.test('multiplicationMatrixVectorInterval')
interface VectorMatrixToVectorCase {
vector: number[];
matrix: number[][];
- expected: (number | IntervalBounds)[];
+ expected: (number | IntervalEndpoints)[];
}
g.test('multiplicationVectorMatrixInterval')
@@ -7897,8 +8020,8 @@ g.test('multiplicationVectorMatrixInterval')
// multiplicationVectorMatrixInterval uses DotIntervalOp for calculating
// intervals, so the testing for dotInterval covers the actual interval
// calculations.
- // Keep all expected result integer no larger than 2047 to ensure that all result is exactly
- // represeantable in both f32 and f16.
+ // Keep all expected result integer no larger than 2047 to ensure that
+ // all result is exactly representable in both f32 and f16.
{
vector: [1, 2],
matrix: [
@@ -8002,7 +8125,7 @@ g.test('multiplicationVectorMatrixInterval')
interface FaceForwardCase {
input: [number[], number[], number[]];
- expected: ((number | IntervalBounds)[] | undefined)[];
+ expected: ((number | IntervalEndpoints)[] | undefined)[];
}
g.test('faceForwardIntervals')
@@ -8081,8 +8204,8 @@ g.test('faceForwardIntervals')
interface ModfCase {
input: number;
- fract: number | IntervalBounds;
- whole: number | IntervalBounds;
+ fract: number | IntervalEndpoints;
+ whole: number | IntervalEndpoints;
}
g.test('modfInterval')
@@ -8135,18 +8258,18 @@ g.test('modfInterval')
interface RefractCase {
input: [number[], number[], number];
- expected: (number | IntervalBounds)[];
+ expected: (number | IntervalEndpoints)[];
}
// Scope for refractInterval tests so that they can have constants for magic
// numbers that don't pollute the global namespace or have unwieldy long names.
{
- const kNegativeOneBounds = {
+ const kNegativeOneEndpoints = {
f32: [
reinterpretU64AsF64(0xbff0_0000_c000_0000n),
reinterpretU64AsF64(0xbfef_ffff_4000_0000n),
- ] as IntervalBounds,
- f16: [reinterpretU16AsF16(0xbc06), reinterpretU16AsF16(0xbbfa)] as IntervalBounds,
+ ] as IntervalEndpoints,
+ f16: [reinterpretU16AsF16(0xbc06), reinterpretU16AsF16(0xbbfa)] as IntervalEndpoints,
} as const;
// prettier-ignore
@@ -8178,7 +8301,7 @@ interface RefractCase {
// vec4
// x = [1, -2, 3, -4], y = [-5, 6, -7, 8], z = 9,
// dot(y, x) = -71, k = 1.0 - 9 * 9 * (1.0 - 71 * 71) = 408241 overflow f16.
- { input: [[1, -2, 3, -4], [-5, 6, -7, 8], 9], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
+ { input: [[1, -2, 3, -4], [-5, 6, -7, 8], 9], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
// x = [1, -2, 3, -4], y = [-5, 4, -3, 2], z = 2.5,
// dot(y, x) = -30, k = 1.0 - 2.5 * 2.5 * (1.0 - 30 * 30) = 5619.75.
// a = z * dot(y, x) + sqrt(k) = ~-0.035, result is about z * x - a * y = [~2.325, ~-4.86, ~7.4025, ~-9.93]
@@ -8205,23 +8328,23 @@ interface RefractCase {
{ input: [[1, 1], [0.1, 0], 10], expected: [0, 0] },
// k contains 0
- { input: [[1, 1], [0.1, 0], 1.005038], expected: [kUnboundedBounds, kUnboundedBounds] },
+ { input: [[1, 1], [0.1, 0], 1.005038], expected: [kUnboundedEndpoints, kUnboundedEndpoints] },
// k > 0
// vec2
- { input: [[1, 1], [1, 0], 1], expected: [kNegativeOneBounds[p.trait], 1] },
+ { input: [[1, 1], [1, 0], 1], expected: [kNegativeOneEndpoints[p.trait], 1] },
// vec3
- { input: [[1, 1, 1], [1, 0, 0], 1], expected: [kNegativeOneBounds[p.trait], 1, 1] },
+ { input: [[1, 1, 1], [1, 0, 0], 1], expected: [kNegativeOneEndpoints[p.trait], 1, 1] },
// vec4
- { input: [[1, 1, 1, 1], [1, 0, 0, 0], 1], expected: [kNegativeOneBounds[p.trait], 1, 1, 1] },
-
- // Test that dot going OOB bounds in the intermediate calculations propagates
- { input: [[constants.positive.nearest_max, constants.positive.max, constants.negative.min], [1.0, 1.0, 1.0], 1], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.positive.nearest_max, constants.negative.min, constants.positive.max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.positive.max, constants.positive.nearest_max, constants.negative.min], [1.0, 1.0, 1.0], 1], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.negative.min, constants.positive.nearest_max, constants.positive.max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.positive.max, constants.negative.min, constants.positive.nearest_max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
- { input: [[constants.negative.min, constants.positive.max, constants.positive.nearest_max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedBounds, kUnboundedBounds, kUnboundedBounds] },
+ { input: [[1, 1, 1, 1], [1, 0, 0, 0], 1], expected: [kNegativeOneEndpoints[p.trait], 1, 1, 1] },
+
+ // Test that dot going OOB in the intermediate calculations propagates
+ { input: [[constants.positive.nearest_max, constants.positive.max, constants.negative.min], [1.0, 1.0, 1.0], 1], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.positive.nearest_max, constants.negative.min, constants.positive.max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.positive.max, constants.positive.nearest_max, constants.negative.min], [1.0, 1.0, 1.0], 1], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.negative.min, constants.positive.nearest_max, constants.positive.max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.positive.max, constants.negative.min, constants.positive.nearest_max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
+ { input: [[constants.negative.min, constants.positive.max, constants.positive.nearest_max], [1.0, 1.0, 1.0], 1], expected: [kUnboundedEndpoints, kUnboundedEndpoints, kUnboundedEndpoints] },
];
})
)
diff --git a/dom/webgpu/tests/cts/checkout/src/unittests/maths.spec.ts b/dom/webgpu/tests/cts/checkout/src/unittests/maths.spec.ts
index 357c574281..d84299d993 100644
--- a/dom/webgpu/tests/cts/checkout/src/unittests/maths.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/unittests/maths.spec.ts
@@ -22,8 +22,8 @@ import {
correctlyRoundedF32,
FlushMode,
frexp,
- fullF16Range,
- fullF32Range,
+ scalarF16Range,
+ scalarF32Range,
fullI32Range,
lerp,
linearRange,
@@ -36,6 +36,7 @@ import {
oneULPF64,
lerpBigInt,
linearRangeBigInt,
+ biasedRangeBigInt,
} from '../webgpu/util/math.js';
import {
reinterpretU16AsF16,
@@ -1525,6 +1526,41 @@ g.test('linearRangeBigInt')
);
});
+g.test('biasedRangeBigInt')
+ .paramsSimple<rangeBigIntCase>(
+ // prettier-ignore
+ [
+ { a: 0n, b: 0n, num_steps: 10, result: new Array<bigint>(10).fill(0n) },
+ { a: 10n, b: 10n, num_steps: 10, result: new Array<bigint>(10).fill(10n) },
+ { a: 0n, b: 10n, num_steps: 1, result: [0n] },
+ { a: 10n, b: 0n, num_steps: 1, result: [10n] },
+ { a: 0n, b: 10n, num_steps: 11, result: [0n, 0n, 0n, 0n, 1n, 2n, 3n, 4n, 6n, 8n, 10n] },
+ { a: 10n, b: 0n, num_steps: 11, result: [10n, 10n, 10n, 10n, 9n, 8n, 7n, 6n, 4n, 2n, 0n] },
+ { a: 0n, b: 1000n, num_steps: 11, result: [0n, 9n, 39n, 89n, 159n, 249n, 359n, 489n, 639n, 809n, 1000n] },
+ { a: 1000n, b: 0n, num_steps: 11, result: [1000n, 991n, 961n, 911n, 841n, 751n, 641n, 511n, 361n, 191n, 0n] },
+ { a: 1n, b: 5n, num_steps: 5, result: [1n, 1n, 2n, 3n, 5n] },
+ { a: 5n, b: 1n, num_steps: 5, result: [5n, 5n, 4n, 3n, 1n] },
+ { a: 0n, b: 10n, num_steps: 5, result: [0n, 0n, 2n, 5n, 10n] },
+ { a: 10n, b: 0n, num_steps: 5, result: [10n, 10n, 8n, 5n, 0n] },
+ { a: -10n, b: 10n, num_steps: 11, result: [-10n, -10n, -10n, -10n, -8n, -6n, -4n, -2n, 2n, 6n, 10n] },
+ { a: 10n, b: -10n, num_steps: 11, result: [10n, 10n, 10n, 10n, 8n, 6n, 4n, 2n, -2n, -6n, -10n] },
+ { a: -10n, b: 0n, num_steps: 11, result: [-10n, -10n, -10n, -10n, -9n, -8n, -7n, -6n, -4n, -2n, -0n] },
+ { a: 0n, b: -10n, num_steps: 11, result: [0n, 0n, 0n, 0n, -1n, -2n, -3n, -4n, -6n, -8n, -10n] },
+ ]
+ )
+ .fn(test => {
+ const a = test.params.a;
+ const b = test.params.b;
+ const num_steps = test.params.num_steps;
+ const got = biasedRangeBigInt(a, b, num_steps);
+ const expect = test.params.result;
+
+ test.expect(
+ objectEquals(got, expect),
+ `biasedRangeBigInt(${a}, ${b}, ${num_steps}) returned ${got}. Expected ${expect}`
+ );
+ });
+
interface fullF32RangeCase {
neg_norm: number;
neg_sub: number;
@@ -1557,7 +1593,7 @@ g.test('fullF32Range')
const neg_sub = test.params.neg_sub;
const pos_sub = test.params.pos_sub;
const pos_norm = test.params.pos_norm;
- const got = fullF32Range({ neg_norm, neg_sub, pos_sub, pos_norm });
+ const got = scalarF32Range({ neg_norm, neg_sub, pos_sub, pos_norm });
const expect = test.params.expect;
test.expect(
@@ -1598,7 +1634,7 @@ g.test('fullF16Range')
const neg_sub = test.params.neg_sub;
const pos_sub = test.params.pos_sub;
const pos_norm = test.params.pos_norm;
- const got = fullF16Range({ neg_norm, neg_sub, pos_sub, pos_norm });
+ const got = scalarF16Range({ neg_norm, neg_sub, pos_sub, pos_norm });
const expect = test.params.expect;
test.expect(
diff --git a/dom/webgpu/tests/cts/checkout/src/unittests/parse_imports.spec.ts b/dom/webgpu/tests/cts/checkout/src/unittests/parse_imports.spec.ts
new file mode 100644
index 0000000000..0efdc0d171
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/unittests/parse_imports.spec.ts
@@ -0,0 +1,79 @@
+export const description = `
+Test for "parseImports" utility.
+`;
+
+import { makeTestGroup } from '../common/framework/test_group.js';
+import { parseImports } from '../common/util/parse_imports.js';
+
+import { UnitTest } from './unit_test.js';
+
+class F extends UnitTest {
+ test(content: string, expect: string[]): void {
+ const got = parseImports('a/b/c.js', content);
+ const expectJoined = expect.join('\n');
+ const gotJoined = got.join('\n');
+ this.expect(
+ expectJoined === gotJoined,
+ `
+expected: ${expectJoined}
+got: ${gotJoined}`
+ );
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('empty').fn(t => {
+ t.test(``, []);
+ t.test(`\n`, []);
+ t.test(`\n\n`, []);
+});
+
+g.test('simple').fn(t => {
+ t.test(`import 'x/y/z.js';`, ['a/b/x/y/z.js']);
+ t.test(`import * as blah from 'x/y/z.js';`, ['a/b/x/y/z.js']);
+ t.test(`import { blah } from 'x/y/z.js';`, ['a/b/x/y/z.js']);
+});
+
+g.test('multiple').fn(t => {
+ t.test(
+ `
+blah blah blah
+import 'x/y/z.js';
+more blah
+import * as blah from 'm/n/o.js';
+extra blah
+import { blah } from '../h.js';
+ending with blah
+`,
+ ['a/b/x/y/z.js', 'a/b/m/n/o.js', 'a/h.js']
+ );
+});
+
+g.test('multiline').fn(t => {
+ t.test(
+ `import {
+ blah
+} from 'x/y/z.js';`,
+ ['a/b/x/y/z.js']
+ );
+ t.test(
+ `import {
+ blahA,
+ blahB,
+} from 'x/y/z.js';`,
+ ['a/b/x/y/z.js']
+ );
+});
+
+g.test('file_characters').fn(t => {
+ t.test(`import '01234_56789.js';`, ['a/b/01234_56789.js']);
+});
+
+g.test('relative_paths').fn(t => {
+ t.test(`import '../x.js';`, ['a/x.js']);
+ t.test(`import '../x/y.js';`, ['a/x/y.js']);
+ t.test(`import '../../x.js';`, ['x.js']);
+ t.test(`import '../../../x.js';`, ['../x.js']);
+ t.test(`import '../../../../x.js';`, ['../../x.js']);
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/unittests/serialization.spec.ts b/dom/webgpu/tests/cts/checkout/src/unittests/serialization.spec.ts
index 9717ba3ecf..ea6ed5e42f 100644
--- a/dom/webgpu/tests/cts/checkout/src/unittests/serialization.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/unittests/serialization.spec.ts
@@ -16,6 +16,8 @@ import {
} from '../webgpu/util/compare.js';
import { kValue } from '../webgpu/util/constants.js';
import {
+ abstractFloat,
+ abstractInt,
bool,
deserializeValue,
f16,
@@ -61,6 +63,18 @@ g.test('value').fn(t => {
u8(kValue.u8.max - 1),
u8(kValue.u8.max - 0),
+ abstractInt(kValue.i64.negative.min),
+ abstractInt(kValue.i64.negative.min + 1n),
+ abstractInt(kValue.i64.negative.min + 2n),
+ abstractInt(kValue.i64.negative.max - 2n),
+ abstractInt(kValue.i64.negative.max - 1n),
+ abstractInt(kValue.i64.positive.min),
+ abstractInt(kValue.i64.positive.min + 1n),
+ abstractInt(kValue.i64.positive.min + 2n),
+ abstractInt(kValue.i64.positive.max - 2n),
+ abstractInt(kValue.i64.positive.max - 1n),
+ abstractInt(kValue.i64.positive.max),
+
i32(kValue.i32.negative.min + 0),
i32(kValue.i32.negative.min + 1),
i32(kValue.i32.negative.min + 2),
@@ -97,6 +111,21 @@ g.test('value').fn(t => {
i8(kValue.i8.positive.max - 1),
i8(kValue.i8.positive.max - 0),
+ abstractFloat(0),
+ abstractFloat(-0),
+ abstractFloat(1),
+ abstractFloat(-1),
+ abstractFloat(0.5),
+ abstractFloat(-0.5),
+ abstractFloat(kValue.f64.positive.max),
+ abstractFloat(kValue.f64.positive.min),
+ abstractFloat(kValue.f64.positive.subnormal.max),
+ abstractFloat(kValue.f64.positive.subnormal.min),
+ abstractFloat(kValue.f64.negative.subnormal.max),
+ abstractFloat(kValue.f64.negative.subnormal.min),
+ abstractFloat(kValue.f64.positive.infinity),
+ abstractFloat(kValue.f64.negative.infinity),
+
f32(0),
f32(-0),
f32(1),
@@ -139,6 +168,13 @@ g.test('value').fn(t => {
[0.0, 1.0],
[2.0, 3.0],
],
+ abstractFloat
+ ),
+ toMatrix(
+ [
+ [0.0, 1.0],
+ [2.0, 3.0],
+ ],
f32
),
toMatrix(
@@ -153,6 +189,13 @@ g.test('value').fn(t => {
[0.0, 1.0, 2.0, 3.0],
[4.0, 5.0, 6.0, 7.0],
],
+ abstractFloat
+ ),
+ toMatrix(
+ [
+ [0.0, 1.0, 2.0, 3.0],
+ [4.0, 5.0, 6.0, 7.0],
+ ],
f32
),
toMatrix(
@@ -169,6 +212,14 @@ g.test('value').fn(t => {
[3.0, 4.0, 5.0],
[6.0, 7.0, 8.0],
],
+ abstractFloat
+ ),
+ toMatrix(
+ [
+ [0.0, 1.0, 2.0],
+ [3.0, 4.0, 5.0],
+ [6.0, 7.0, 8.0],
+ ],
f32
),
toMatrix(
@@ -186,6 +237,15 @@ g.test('value').fn(t => {
[4.0, 5.0],
[6.0, 7.0],
],
+ abstractFloat
+ ),
+ toMatrix(
+ [
+ [0.0, 1.0],
+ [2.0, 3.0],
+ [4.0, 5.0],
+ [6.0, 7.0],
+ ],
f32
),
toMatrix(
@@ -204,6 +264,15 @@ g.test('value').fn(t => {
[8.0, 9.0, 10.0, 11.0],
[12.0, 13.0, 14.0, 15.0],
],
+ abstractFloat
+ ),
+ toMatrix(
+ [
+ [0.0, 1.0, 2.0, 3.0],
+ [4.0, 5.0, 6.0, 7.0],
+ [8.0, 9.0, 10.0, 11.0],
+ [12.0, 13.0, 14.0, 15.0],
+ ],
f32
),
]) {
diff --git a/dom/webgpu/tests/cts/checkout/src/unittests/texture_ok.spec.ts b/dom/webgpu/tests/cts/checkout/src/unittests/texture_ok.spec.ts
index f1e6971a74..c126832b8d 100644
--- a/dom/webgpu/tests/cts/checkout/src/unittests/texture_ok.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/unittests/texture_ok.spec.ts
@@ -4,7 +4,6 @@ Test for texture_ok utils.
import { makeTestGroup } from '../common/framework/test_group.js';
import { typedArrayFromParam, typedArrayParam } from '../common/util/util.js';
-import { RegularTextureFormat } from '../webgpu/format_info.js';
import { TexelView } from '../webgpu/util/texture/texel_view.js';
import { findFailedPixels } from '../webgpu/util/texture/texture_ok.js';
@@ -30,103 +29,103 @@ g.test('findFailedPixels')
u.combineWithParams([
// Sanity Check
{
- format: 'rgba8unorm' as RegularTextureFormat,
+ format: 'rgba8unorm',
actual: typedArrayParam('Uint8Array', [0x00, 0x40, 0x80, 0xff]),
expected: typedArrayParam('Uint8Array', [0x00, 0x40, 0x80, 0xff]),
isSame: true,
},
// Slightly different values
{
- format: 'rgba8unorm' as RegularTextureFormat,
+ format: 'rgba8unorm',
actual: typedArrayParam('Uint8Array', [0x00, 0x40, 0x80, 0xff]),
expected: typedArrayParam('Uint8Array', [0x00, 0x40, 0x81, 0xff]),
isSame: false,
},
// Different representations of the same value
{
- format: 'rgb9e5ufloat' as RegularTextureFormat,
+ format: 'rgb9e5ufloat',
actual: typedArrayParam('Uint8Array', [0x78, 0x56, 0x34, 0x12]),
expected: typedArrayParam('Uint8Array', [0xf0, 0xac, 0x68, 0x0c]),
isSame: true,
},
// Slightly different values
{
- format: 'rgb9e5ufloat' as RegularTextureFormat,
+ format: 'rgb9e5ufloat',
actual: typedArrayParam('Uint8Array', [0x78, 0x56, 0x34, 0x12]),
expected: typedArrayParam('Uint8Array', [0xf1, 0xac, 0x68, 0x0c]),
isSame: false,
},
// Test NaN === NaN
{
- format: 'r32float' as RegularTextureFormat,
+ format: 'r32float',
actual: typedArrayParam('Float32Array', [parseFloat('abc')]),
expected: typedArrayParam('Float32Array', [parseFloat('def')]),
isSame: true,
},
// Sanity Check
{
- format: 'r32float' as RegularTextureFormat,
+ format: 'r32float',
actual: typedArrayParam('Float32Array', [1.23]),
expected: typedArrayParam('Float32Array', [1.23]),
isSame: true,
},
// Slightly different values.
{
- format: 'r32float' as RegularTextureFormat,
+ format: 'r32float',
actual: typedArrayParam('Uint32Array', [0x3f9d70a4]),
expected: typedArrayParam('Uint32Array', [0x3f9d70a5]),
isSame: false,
},
// Slightly different
{
- format: 'rg11b10ufloat' as RegularTextureFormat,
+ format: 'rg11b10ufloat',
actual: typedArrayParam('Uint32Array', [0x3ce]),
expected: typedArrayParam('Uint32Array', [0x3cf]),
isSame: false,
},
// Positive.Infinity === Positive.Infinity (red)
{
- format: 'rg11b10ufloat' as RegularTextureFormat,
+ format: 'rg11b10ufloat',
actual: typedArrayParam('Uint32Array', [0b11111000000]),
expected: typedArrayParam('Uint32Array', [0b11111000000]),
isSame: true,
},
// Positive.Infinity === Positive.Infinity (green)
{
- format: 'rg11b10ufloat' as RegularTextureFormat,
+ format: 'rg11b10ufloat',
actual: typedArrayParam('Uint32Array', [0b11111000000_00000000000]),
expected: typedArrayParam('Uint32Array', [0b11111000000_00000000000]),
isSame: true,
},
// Positive.Infinity === Positive.Infinity (blue)
{
- format: 'rg11b10ufloat' as RegularTextureFormat,
+ format: 'rg11b10ufloat',
actual: typedArrayParam('Uint32Array', [0b1111100000_00000000000_00000000000]),
expected: typedArrayParam('Uint32Array', [0b1111100000_00000000000_00000000000]),
isSame: true,
},
// NaN === NaN (red)
{
- format: 'rg11b10ufloat' as RegularTextureFormat,
+ format: 'rg11b10ufloat',
actual: typedArrayParam('Uint32Array', [0b11111000001]),
expected: typedArrayParam('Uint32Array', [0b11111000010]),
isSame: true,
},
// NaN === NaN (green)
{
- format: 'rg11b10ufloat' as RegularTextureFormat,
+ format: 'rg11b10ufloat',
actual: typedArrayParam('Uint32Array', [0b11111000100_00000000000]),
expected: typedArrayParam('Uint32Array', [0b11111001000_00000000000]),
isSame: true,
},
// NaN === NaN (blue)
{
- format: 'rg11b10ufloat' as RegularTextureFormat,
+ format: 'rg11b10ufloat',
actual: typedArrayParam('Uint32Array', [0b1111110000_00000000000_00000000000]),
expected: typedArrayParam('Uint32Array', [0b1111101000_00000000000_00000000000]),
isSame: true,
},
- ])
+ ] as const)
)
.fn(t => {
const { format, actual, expected, isSame } = t.params;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/adapter/requestDevice.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/adapter/requestDevice.spec.ts
index 314da6356e..51ab2303eb 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/adapter/requestDevice.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/adapter/requestDevice.spec.ts
@@ -311,6 +311,65 @@ g.test('limit,better_than_supported')
t.shouldReject('OperationError', adapter.requestDevice({ requiredLimits }));
});
+g.test('limit,out_of_range')
+ .desc(
+ `
+ Test that specifying limits that are out of range (<0, >MAX_SAFE_INTEGER, >2**31-2 for 32-bit
+ limits, =0 for alignment limits) produce the appropriate error (TypeError or OperationError).
+ `
+ )
+ .params(u =>
+ u
+ .combine('limit', kLimits)
+ .beginSubcases()
+ .expand('value', function* () {
+ yield -(2 ** 64);
+ yield Number.MIN_SAFE_INTEGER - 3;
+ yield Number.MIN_SAFE_INTEGER - 1;
+ yield Number.MIN_SAFE_INTEGER;
+ yield -(2 ** 32);
+ yield -1;
+ yield 0;
+ yield 2 ** 32 - 2;
+ yield 2 ** 32 - 1;
+ yield 2 ** 32;
+ yield 2 ** 32 + 1;
+ yield 2 ** 32 + 2;
+ yield Number.MAX_SAFE_INTEGER;
+ yield Number.MAX_SAFE_INTEGER + 1;
+ yield Number.MAX_SAFE_INTEGER + 3;
+ yield 2 ** 64;
+ yield Number.MAX_VALUE;
+ })
+ )
+ .fn(async t => {
+ const { limit, value } = t.params;
+
+ const gpu = getGPU(t.rec);
+ const adapter = await gpu.requestAdapter();
+ assert(adapter !== null);
+ const limitInfo = getDefaultLimitsForAdapter(adapter)[limit];
+
+ const requiredLimits = {
+ [limit]: value,
+ };
+
+ const errorName =
+ value < 0 || value > Number.MAX_SAFE_INTEGER
+ ? 'TypeError'
+ : limitInfo.class === 'maximum' && value > adapter.limits[limit]
+ ? 'OperationError'
+ : limitInfo.class === 'alignment' && (value > 2 ** 31 || !isPowerOfTwo(value))
+ ? 'OperationError'
+ : false;
+
+ if (errorName) {
+ t.shouldReject(errorName, adapter.requestDevice({ requiredLimits }));
+ } else {
+ await adapter.requestDevice({ requiredLimits });
+ }
+ });
+
g.test('limit,worse_than_default')
.desc(
`
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts
index 4c55b5162f..edf08c3840 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/copyTextureToTexture.spec.ts
@@ -1,7 +1,7 @@
export const description = `copyTextureToTexture operation tests`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
-import { assert, memcpy, unreachable } from '../../../../common/util/util.js';
+import { assert, ErrorWithExtra, memcpy } from '../../../../common/util/util.js';
import {
kBufferSizeAlignment,
kMinDynamicBufferOffsetAlignment,
@@ -18,14 +18,18 @@ import {
ColorTextureFormat,
isCompressedTextureFormat,
viewCompatible,
+ RegularTextureFormat,
+ isRegularTextureFormat,
} from '../../../format_info.js';
import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
import { makeBufferWithContents } from '../../../util/buffer.js';
-import { checkElementsEqual, checkElementsEqualEither } from '../../../util/check_contents.js';
+import { checkElementsEqual } from '../../../util/check_contents.js';
import { align } from '../../../util/math.js';
import { physicalMipSize } from '../../../util/texture/base.js';
import { DataArrayGenerator } from '../../../util/texture/data_generation.js';
import { kBytesPerRowAlignment, dataBytesForCopyOrFail } from '../../../util/texture/layout.js';
+import { TexelView } from '../../../util/texture/texel_view.js';
+import { findFailedPixels } from '../../../util/texture/texture_ok.js';
const dataGenerator = new DataArrayGenerator();
@@ -207,7 +211,7 @@ class F extends TextureTestMixin(GPUTest) {
align(dstBlocksPerRow * bytesPerBlock, 4);
if (isCompressedTextureFormat(dstTexture.format) && this.isCompatibility) {
- assert(viewCompatible(srcFormat, dstFormat));
+ assert(viewCompatible(this.isCompatibility, srcFormat, dstFormat));
// compare by rendering. We need the expected texture to match
// the dstTexture so we'll create a texture where we supply
// all of the data in JavaScript.
@@ -304,6 +308,7 @@ class F extends TextureTestMixin(GPUTest) {
y: appliedDstOffset.y / blockHeight,
z: appliedDstOffset.z,
};
+ const bytesInRow = appliedCopyBlocksPerRow * bytesPerBlock;
for (let z = 0; z < appliedCopyDepth; ++z) {
const srcOffsetZ = srcCopyOffsetInBlocks.z + z;
@@ -321,7 +326,6 @@ class F extends TextureTestMixin(GPUTest) {
(srcBlockRowsPerImage * srcOffsetZ + srcOffsetYInBlocks) +
srcCopyOffsetInBlocks.x * bytesPerBlock;
- const bytesInRow = appliedCopyBlocksPerRow * bytesPerBlock;
memcpy(
{ src: expectedUint8Data, start: expectedDataOffset, length: bytesInRow },
{ dst: expectedUint8DataWithPadding, start: expectedDataWithPaddingOffset }
@@ -329,46 +333,69 @@ class F extends TextureTestMixin(GPUTest) {
}
}
- let alternateExpectedData = expectedUint8DataWithPadding;
- // For 8-byte snorm formats, allow an alternative encoding of -1.
- // MAINTENANCE_TODO: Use textureContentIsOKByT2B with TexelView.
- if (srcFormat.includes('snorm')) {
- switch (srcFormat) {
- case 'r8snorm':
- case 'rg8snorm':
- case 'rgba8snorm':
- alternateExpectedData = alternateExpectedData.slice();
- for (let i = 0; i < alternateExpectedData.length; ++i) {
- if (alternateExpectedData[i] === 128) {
- alternateExpectedData[i] = 129;
- } else if (alternateExpectedData[i] === 129) {
- alternateExpectedData[i] = 128;
- }
- }
- break;
- case 'bc4-r-snorm':
- case 'bc5-rg-snorm':
- case 'eac-r11snorm':
- case 'eac-rg11snorm':
- break;
- default:
- unreachable();
- }
+ if (isCompressedTextureFormat(dstFormat)) {
+ this.expectGPUBufferValuesPassCheck(
+ dstBuffer,
+ vals => checkElementsEqual(vals, expectedUint8DataWithPadding),
+ {
+ srcByteOffset: 0,
+ type: Uint8Array,
+ typedLength: expectedUint8DataWithPadding.length,
+ }
+ );
+ return;
}
+ assert(isRegularTextureFormat(dstFormat));
+ const regularDstFormat = dstFormat as RegularTextureFormat;
+
// Verify the content of the whole subresource of dstTexture at dstCopyLevel (in dstBuffer) is expected.
- this.expectGPUBufferValuesPassCheck(
- dstBuffer,
- alternateExpectedData === expectedUint8DataWithPadding
- ? vals => checkElementsEqual(vals, expectedUint8DataWithPadding)
- : vals =>
- checkElementsEqualEither(vals, [expectedUint8DataWithPadding, alternateExpectedData]),
- {
- srcByteOffset: 0,
- type: Uint8Array,
- typedLength: expectedUint8DataWithPadding.length,
+ const checkByTextureFormat = (actual: Uint8Array) => {
+ const zero = { x: 0, y: 0, z: 0 };
+
+ const actTexelView = TexelView.fromTextureDataByReference(regularDstFormat, actual, {
+ bytesPerRow: bytesInRow,
+ rowsPerImage: dstBlockRowsPerImage,
+ subrectOrigin: zero,
+ subrectSize: dstTextureSizeAtLevel,
+ });
+ const expTexelView = TexelView.fromTextureDataByReference(
+ regularDstFormat,
+ expectedUint8DataWithPadding,
+ {
+ bytesPerRow: bytesInRow,
+ rowsPerImage: dstBlockRowsPerImage,
+ subrectOrigin: zero,
+ subrectSize: dstTextureSizeAtLevel,
+ }
+ );
+
+ const failedPixelsMessage = findFailedPixels(
+ regularDstFormat,
+ zero,
+ dstTextureSizeAtLevel,
+ { actTexelView, expTexelView },
+ {
+ maxFractionalDiff: 0,
+ }
+ );
+
+ if (failedPixelsMessage !== undefined) {
+ const msg = 'Texture level had unexpected contents:\n' + failedPixelsMessage;
+ return new ErrorWithExtra(msg, () => ({
+ expTexelView,
+ actTexelView,
+ }));
}
- );
+
+ return undefined;
+ };
+
+ this.expectGPUBufferValuesPassCheck(dstBuffer, checkByTextureFormat, {
+ srcByteOffset: 0,
+ type: Uint8Array,
+ typedLength: expectedUint8DataWithPadding.length,
+ });
}
InitializeStencilAspect(
@@ -1349,6 +1376,9 @@ g.test('copy_multisampled_color')
texture can only be 1.
`
)
+ .beforeAllSubcases(t => {
+ t.skipIf(t.isCompatibility, 'multisample textures are not copyable in compatibility mode');
+ })
.fn(t => {
const textureSize = [32, 16, 1] as const;
const kColorFormat = 'rgba8unorm';
@@ -1537,6 +1567,9 @@ g.test('copy_multisampled_depth')
texture can only be 1.
`
)
+ .beforeAllSubcases(t => {
+ t.skipIf(t.isCompatibility, 'multisample textures are not copyable in compatibility mode');
+ })
.fn(t => {
const textureSize = [32, 16, 1] as const;
const kDepthFormat = 'depth24plus';
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/image_copy.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/image_copy.spec.ts
index 4eebc3d611..cff2bd50d5 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/image_copy.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/image_copy.spec.ts
@@ -23,8 +23,9 @@ export const description = `writeTexture + copyBufferToTexture + copyTextureToBu
* copy_with_no_image_or_slice_padding_and_undefined_values: test that when copying a single row we can set any bytesPerRow value and when copying a single\
slice we can set rowsPerImage to 0. Also test setting offset, rowsPerImage, mipLevel, origin, origin.{x,y,z} to undefined.
+Note: more coverage of memory synchronization for different read and write texture methods are in same_subresource.spec.ts.
+
* TODO:
- - add another initMethod which renders the texture [3]
- test copyT2B with buffer size not divisible by 4 (not done because expectContents 4-byte alignment)
- Convert the float32 values in initialData into the ones compatible to the depth aspect of
depthFormats when depth16unorm is supported by the browsers in
@@ -86,7 +87,7 @@ type InitMethod = 'WriteTexture' | 'CopyB2T';
* - PartialCopyT2B: do CopyT2B to check that the part of the texture we copied to with InitMethod
* matches the data we were copying and that we don't overwrite any data in the target buffer that
* we're not supposed to - that's primarily for testing CopyT2B functionality.
- * - FullCopyT2B: do CopyT2B on the whole texture and check wether the part we copied to matches
+ * - FullCopyT2B: do CopyT2B on the whole texture and check whether the part we copied to matches
* the data we were copying and that the nothing else was modified - that's primarily for testing
* WriteTexture and CopyB2T.
*
@@ -1132,6 +1133,10 @@ class ImageCopyTest extends TextureTestMixin(GPUTest) {
copySize
);
+ const use2DArray = this.isCompatibility && inputTexture.depthOrArrayLayers > 1;
+ const [textureType, layerCode] = use2DArray
+ ? ['texture_2d_array', ', baseArrayLayer']
+ : ['texture_2d', ''];
const renderPipeline = this.device.createRenderPipeline({
layout: 'auto',
vertex: {
@@ -1154,10 +1159,11 @@ class ImageCopyTest extends TextureTestMixin(GPUTest) {
fragment: {
module: this.device.createShaderModule({
code: `
- @group(0) @binding(0) var inputTexture: texture_2d<f32>;
+ @group(0) @binding(0) var inputTexture: ${textureType}<f32>;
+ @group(0) @binding(1) var<uniform> baseArrayLayer: u32;
@fragment fn main(@builtin(position) fragcoord : vec4<f32>) ->
@builtin(frag_depth) f32 {
- var depthValue : vec4<f32> = textureLoad(inputTexture, vec2<i32>(fragcoord.xy), 0);
+ var depthValue : vec4<f32> = textureLoad(inputTexture, vec2<i32>(fragcoord.xy)${layerCode}, 0);
return depthValue.x;
}`,
}),
@@ -1200,19 +1206,26 @@ class ImageCopyTest extends TextureTestMixin(GPUTest) {
});
renderPass.setPipeline(renderPipeline);
+ const uniformBufferEntry = use2DArray
+ ? [this.createUniformBufferAndBindGroupEntryForBaseArrayLayer(z)]
+ : [];
+
const bindGroup = this.device.createBindGroup({
layout: renderPipeline.getBindGroupLayout(0),
entries: [
{
binding: 0,
resource: inputTexture.createView({
- dimension: '2d',
- baseArrayLayer: z,
- arrayLayerCount: 1,
+ dimension: use2DArray ? '2d-array' : '2d',
+ ...(!use2DArray && {
+ baseArrayLayer: z,
+ arrayLayerCount: 1,
+ }),
baseMipLevel: 0,
mipLevelCount: 1,
}),
},
+ ...uniformBufferEntry,
],
});
renderPass.setBindGroup(0, bindGroup);
@@ -1223,6 +1236,23 @@ class ImageCopyTest extends TextureTestMixin(GPUTest) {
this.queue.submit([encoder.finish()]);
}
+ createUniformBufferAndBindGroupEntryForBaseArrayLayer(z: number) {
+ const buffer = this.device.createBuffer({
+ usage: GPUBufferUsage.UNIFORM,
+ size: 4,
+ mappedAtCreation: true,
+ });
+ this.trackForCleanup(buffer);
+ new Uint32Array(buffer.getMappedRange()).set([z]);
+ buffer.unmap();
+ return {
+ binding: 1,
+ resource: {
+ buffer,
+ },
+ };
+ }
+
DoCopyTextureToBufferWithDepthAspectTest(
format: DepthStencilFormat,
copySize: readonly [number, number, number],
@@ -1328,8 +1358,6 @@ class ImageCopyTest extends TextureTestMixin(GPUTest) {
/**
* This is a helper function used for filtering test parameters
- *
- * [3]: Modify this after introducing tests with rendering.
*/
function formatCanBeTested({ format }: { format: ColorTextureFormat }): boolean {
return kTextureFormatInfo[format].color.copyDst && kTextureFormatInfo[format].color.copySrc;
@@ -1491,6 +1519,12 @@ works for every format with 2d and 2d-array textures.
offset + bytesInCopyExtentPerRow { ==, > } bytesPerRow
offset > bytesInACompleteCopyImage
+ Covers spceial cases for OpenGL Compat:
+ offset % 4 > 0 while:
+ - padding bytes at end of each row/layer: bytesPerRow % 256 > 0 || rowsPerImage > copyDepth
+ - rows/layers are compact: bytesPerRow % 256 == 0 && rowsPerImage == copyDepth
+ - padding bytes at front and end of the same 4-byte word: format == 'r8snorm' && copyWidth <= 2
+
TODO: Cover the special code paths for 3D textures in D3D12.
TODO: Make a variant for depth-stencil formats.
`
@@ -1505,6 +1539,18 @@ works for every format with 2d and 2d-array textures.
.beginSubcases()
.combineWithParams(kOffsetsAndSizesParams.offsetsAndPaddings)
.combine('copyDepth', kOffsetsAndSizesParams.copyDepth) // 2d and 2d-array textures
+ .combine('copyWidth', [3, 1, 2, 127, 128, 255, 256] as const) // copyWidth === 3 is the default. Others covers special cases for r8snorm and rg8snorm on compatiblity mode.
+ .filter(({ format, copyWidth }) => {
+ switch (format) {
+ case 'r8snorm':
+ case 'rg8snorm':
+ return true;
+ default:
+ // Restrict test parameters to save run time.
+ return copyWidth === 3;
+ }
+ })
+ .combine('rowsPerImageEqualsCopyHeight', [true, false] as const)
.unless(p => p.dimension === '1d' && p.copyDepth !== 1)
)
.beforeAllSubcases(t => {
@@ -1521,25 +1567,43 @@ works for every format with 2d and 2d-array textures.
dimension,
initMethod,
checkMethod,
+ copyWidth,
+ rowsPerImageEqualsCopyHeight,
} = t.params;
+
+ // Skip test cases designed for special cases coverage on compatibility mode to save run time.
+ if (!(t.isCompatibility && (format === 'r8snorm' || format === 'rg8snorm'))) {
+ if (rowsPerImageEqualsCopyHeight === false) {
+ t.skip(
+ 'rowsPerImageEqualsCopyHeight === false is only for r8snorm and rg8snorm on compatibility mode'
+ );
+ }
+
+ if (copyWidth !== 3) {
+ t.skip('copyWidth !== 3 is only for r8snorm and rg8snorm on compatibility mode');
+ }
+ }
+
const info = kTextureFormatInfo[format];
const offset = offsetInBlocks * info.color.bytes;
+ const copyHeight = 3;
const copySize = {
- width: 3 * info.blockWidth,
- height: 3 * info.blockHeight,
+ width: copyWidth * info.blockWidth,
+ height: copyHeight * info.blockHeight,
depthOrArrayLayers: copyDepth,
};
let textureHeight = 4 * info.blockHeight;
- let rowsPerImage = 3;
- const bytesPerRow = 256;
+ let rowsPerImage = rowsPerImageEqualsCopyHeight ? copyHeight : copyHeight + 1;
+ const bytesPerRow = align(copyWidth * info.color.bytes, 256);
if (dimension === '1d') {
copySize.height = 1;
textureHeight = info.blockHeight;
rowsPerImage = 1;
}
- const textureSize = [4 * info.blockWidth, textureHeight, copyDepth] as const;
+ // Add textureWidth by 1 to make sure we are doing a partial copy.
+ const textureSize = [(copyWidth + 1) * info.blockWidth, textureHeight, copyDepth] as const;
const minDataSize = dataBytesForCopyOrFail({
layout: { offset, bytesPerRow, rowsPerImage },
@@ -1549,7 +1613,7 @@ works for every format with 2d and 2d-array textures.
});
const dataSize = minDataSize + dataPaddingInBytes;
- // We're copying a (3 x 3 x copyDepth) (in texel blocks) part of a (4 x 4 x copyDepth)
+ // We're copying a (copyWidth x 3 x copyDepth) (in texel blocks) part of a ((copyWidth + 1) x 4 x copyDepth)
// (in texel blocks) texture with no origin.
t.uploadTextureAndVerifyCopy({
textureDataLayout: { offset, bytesPerRow, rowsPerImage },
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/queries/occlusionQuery.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/queries/occlusionQuery.spec.ts
index 39b7a377fe..c4b80b7f4f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/queries/occlusionQuery.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/command_buffer/queries/occlusionQuery.spec.ts
@@ -695,7 +695,7 @@ g.test('occlusion_query,scissor')
const expectPassed = !occluded;
t.expect(
!!passed === expectPassed,
- `queryIndex: ${queryIndex}, scissorCase: ${scissorCase}, was: ${!!passed}, expected: ${expectPassed}, ${name}`
+ `queryIndex: ${queryIndex}, scissorCase: ${scissorCase}, was: ${!!passed}, expected: ${expectPassed}`
);
}
);
@@ -739,7 +739,7 @@ g.test('occlusion_query,depth')
const expectPassed = queryIndex % 2 === 0;
t.expect(
!!passed === expectPassed,
- `queryIndex: ${queryIndex}, was: ${!!passed}, expected: ${expectPassed}, ${name}`
+ `queryIndex: ${queryIndex}, was: ${!!passed}, expected: ${expectPassed}`
);
}
);
@@ -783,7 +783,7 @@ g.test('occlusion_query,stencil')
const expectPassed = queryIndex % 2 === 0;
t.expect(
!!passed === expectPassed,
- `queryIndex: ${queryIndex}, was: ${!!passed}, expected: ${expectPassed}, ${name}`
+ `queryIndex: ${queryIndex}, was: ${!!passed}, expected: ${expectPassed}`
);
}
);
@@ -851,7 +851,7 @@ g.test('occlusion_query,sample_mask')
const expectPassed = !!(sampleMask & drawMask);
t.expect(
!!passed === expectPassed,
- `queryIndex: ${queryIndex}, was: ${!!passed}, expected: ${expectPassed}, ${name}`
+ `queryIndex: ${queryIndex}, was: ${!!passed}, expected: ${expectPassed}`
);
}
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/memory_sync/texture/readonly_depth_stencil.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/memory_sync/texture/readonly_depth_stencil.spec.ts
new file mode 100644
index 0000000000..3c381f1c1f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/memory_sync/texture/readonly_depth_stencil.spec.ts
@@ -0,0 +1,329 @@
+export const description = `
+Memory synchronization tests for depth-stencil attachments in a single pass, with checks for readonlyness.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { kDepthStencilFormats, kTextureFormatInfo } from '../../../../format_info.js';
+import { GPUTest } from '../../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('sampling_while_testing')
+ .desc(
+ `Tests concurrent sampling and testing of readonly depth-stencil attachments in a render pass.
+ - Test for all depth-stencil formats.
+ - Test for all valid combinations of depth/stencilReadOnly.
+
+In particular this test checks that a non-readonly aspect can be rendered to, and used for depth/stencil
+testing while the other one is used for sampling.
+ `
+ )
+ .params(p =>
+ p
+ .combine('format', kDepthStencilFormats) //
+ .combine('depthReadOnly', [true, false, undefined])
+ .combine('stencilReadOnly', [true, false, undefined])
+ .filter(p => {
+ const info = kTextureFormatInfo[p.format];
+ const depthMatch = (info.depth === undefined) === (p.depthReadOnly === undefined);
+ const stencilMatch = (info.stencil === undefined) === (p.stencilReadOnly === undefined);
+ return depthMatch && stencilMatch;
+ })
+ )
+ .beforeAllSubcases(t => {
+ const { format } = t.params;
+ const formatInfo = kTextureFormatInfo[format];
+ const hasDepth = formatInfo.depth !== undefined;
+ const hasStencil = formatInfo.stencil !== undefined;
+
+ t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
+ t.skipIf(
+ t.isCompatibility && hasDepth && hasStencil,
+ 'compatibility mode does not support different TEXTURE_BINDING views of the same texture in a single draw calls'
+ );
+ })
+ .fn(t => {
+ const { format, depthReadOnly, stencilReadOnly } = t.params;
+ const formatInfo = kTextureFormatInfo[format];
+ const hasDepth = formatInfo.depth !== undefined;
+ const hasStencil = formatInfo.stencil !== undefined;
+
+ // The 3x3 depth stencil texture used for the tests.
+ const ds = t.device.createTexture({
+ label: 'testTexture',
+ size: [3, 3],
+ format,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
+ });
+ t.trackForCleanup(ds);
+
+ // Fill the texture along the X axis with stencil values 1, 2, 3 and along the Y axis depth
+ // values 0.1, 0.2, 0.3. The depth value is written using @builtin(frag_depth) while the
+ // stencil is written using stencil operation and modifying the stencilReference.
+ const initModule = t.device.createShaderModule({
+ code: `
+ @vertex fn vs(
+ @builtin(instance_index) x : u32, @builtin(vertex_index) y : u32
+ ) -> @builtin(position) vec4f {
+ let texcoord = (vec2f(f32(x), f32(y)) + vec2f(0.5)) / 3;
+ return vec4f((texcoord * 2) - vec2f(1.0), 0, 1);
+ }
+ @fragment fn fs_with_depth(@builtin(position) pos : vec4f) -> @builtin(frag_depth) f32 {
+ return (pos.y + 0.5) / 10;
+ }
+ @fragment fn fs_no_depth() {
+ }
+ `,
+ });
+ const initPipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ label: 'initPipeline',
+ vertex: { module: initModule },
+ fragment: {
+ module: initModule,
+ targets: [],
+ entryPoint: hasDepth ? 'fs_with_depth' : 'fs_no_depth',
+ },
+ depthStencil: {
+ format,
+ ...(hasDepth && {
+ depthWriteEnabled: true,
+ depthCompare: 'always',
+ }),
+ ...(hasStencil && {
+ stencilBack: { compare: 'always', passOp: 'replace' },
+ stencilFront: { compare: 'always', passOp: 'replace' },
+ }),
+ },
+ primitive: { topology: 'point-list' },
+ });
+
+ const encoder = t.device.createCommandEncoder();
+
+ const initPass = encoder.beginRenderPass({
+ colorAttachments: [],
+ depthStencilAttachment: {
+ view: ds.createView(),
+ ...(hasDepth && {
+ depthStoreOp: 'store',
+ depthLoadOp: 'clear',
+ depthClearValue: 0,
+ }),
+ ...(hasStencil && {
+ stencilStoreOp: 'store',
+ stencilLoadOp: 'clear',
+ stencilClearValue: 0,
+ }),
+ },
+ });
+ initPass.setPipeline(initPipeline);
+ for (let i = 0; i < 3; i++) {
+ initPass.setStencilReference(i + 1);
+ // Draw 3 points (Y = 0, 1, 2) at X = instance_index = i.
+ initPass.draw(3, 1, 0, i);
+ }
+ initPass.end();
+
+ // Perform the actual test:
+ // - The shader outputs depth 0.15 and stencil 2 (via stencilReference).
+ // - Test that the fragdepth / stencilref must be <= to what's in the depth-stencil attachment.
+ // -> Fragments that have depth 0.1 or stencil 1 are tested out.
+ // - Test that sampling the depth / stencil (when possible) is <= 0.2 for depth, <= 2 for stencil
+ // -> Fragments that have depth 0.3 or stencil 3 are discarded if that aspect is readonly.
+ // - Write the depth / increment the stencil if the aspect is not readonly.
+ // -> After the test, fragments that passed will have non-readonly aspects updated.
+ const kFragDepth = 0.15;
+ const kStencilRef = 2;
+ const testAndCheckModule = t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var depthTex : texture_2d<f32>;
+ @group(0) @binding(1) var stencilTex : texture_2d<u32>;
+
+ @vertex fn full_quad_vs(@builtin(vertex_index) id : u32) -> @builtin(position) vec4f {
+ let pos = array(vec2f(-3, -1), vec2(3, -1), vec2(0, 2));
+ return vec4f(pos[id], ${kFragDepth}, 1.0);
+ }
+
+ @fragment fn test_texture(@builtin(position) pos : vec4f) {
+ let texel = vec2u(floor(pos.xy));
+ if ${!!stencilReadOnly} && textureLoad(stencilTex, texel, 0).r > 2 {
+ discard;
+ }
+ if ${!!depthReadOnly} && textureLoad(depthTex, texel, 0).r > 0.21 {
+ discard;
+ }
+ }
+
+ @fragment fn check_texture(@builtin(position) pos : vec4f) -> @location(0) u32 {
+ let texel = vec2u(floor(pos.xy));
+
+ // The current values in the framebuffer.
+ let initStencil = texel.x + 1;
+ let initDepth = f32(texel.y + 1) / 10.0;
+
+ // Expected results of the test_texture step.
+ let stencilTestPasses = !${hasStencil} || ${kStencilRef} <= initStencil;
+ let depthTestPasses = !${hasDepth} || ${kFragDepth} <= initDepth;
+ let fsDiscards = (${!!stencilReadOnly} && initStencil > 2) ||
+ (${!!depthReadOnly} && initDepth > 0.21);
+
+ // Compute the values that should be in the framebuffer.
+ var stencil = initStencil;
+ var depth = initDepth;
+
+ // When the fragments aren't discarded, fragment output operations happen.
+ if depthTestPasses && stencilTestPasses && !fsDiscards {
+ if ${!stencilReadOnly} {
+ stencil += 1;
+ }
+ if ${!depthReadOnly} {
+ depth = ${kFragDepth};
+ }
+ }
+
+ if ${hasStencil} && textureLoad(stencilTex, texel, 0).r != stencil {
+ return 0;
+ }
+ if ${hasDepth} && abs(textureLoad(depthTex, texel, 0).r - depth) > 0.01 {
+ return 0;
+ }
+ return 1;
+ }
+ `,
+ });
+ const testPipeline = t.device.createRenderPipeline({
+ label: 'testPipeline',
+ layout: 'auto',
+ vertex: { module: testAndCheckModule },
+ fragment: { module: testAndCheckModule, entryPoint: 'test_texture', targets: [] },
+ depthStencil: {
+ format,
+ ...(hasDepth && {
+ depthCompare: 'less-equal',
+ depthWriteEnabled: !depthReadOnly,
+ }),
+ ...(hasStencil && {
+ stencilBack: {
+ compare: 'less-equal',
+ passOp: stencilReadOnly ? 'keep' : 'increment-clamp',
+ },
+ stencilFront: {
+ compare: 'less-equal',
+ passOp: stencilReadOnly ? 'keep' : 'increment-clamp',
+ },
+ }),
+ },
+ primitive: { topology: 'triangle-list' },
+ });
+
+ // Make fake stencil or depth textures to put in the bindgroup if the aspect is not readonly.
+ const fakeStencil = t.device.createTexture({
+ label: 'fakeStencil',
+ format: 'r32uint',
+ size: [1, 1],
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ });
+ t.trackForCleanup(fakeStencil);
+ const fakeDepth = t.device.createTexture({
+ label: 'fakeDepth',
+ format: 'r32float',
+ size: [1, 1],
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ });
+ t.trackForCleanup(fakeDepth);
+ const stencilView = stencilReadOnly
+ ? ds.createView({ aspect: 'stencil-only' })
+ : fakeStencil.createView();
+ const depthView = depthReadOnly
+ ? ds.createView({ aspect: 'depth-only' })
+ : fakeDepth.createView();
+ const testBindGroup = t.device.createBindGroup({
+ layout: testPipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: depthView },
+ { binding: 1, resource: stencilView },
+ ],
+ });
+
+ // Run the test.
+ const testPass = encoder.beginRenderPass({
+ colorAttachments: [],
+ depthStencilAttachment: {
+ view: ds.createView(),
+ ...(hasDepth &&
+ (depthReadOnly
+ ? { depthReadOnly: true }
+ : {
+ depthStoreOp: 'store',
+ depthLoadOp: 'load',
+ })),
+ ...(hasStencil &&
+ (stencilReadOnly
+ ? { stencilReadOnly: true }
+ : {
+ stencilStoreOp: 'store',
+ stencilLoadOp: 'load',
+ })),
+ },
+ });
+ testPass.setPipeline(testPipeline);
+ testPass.setStencilReference(kStencilRef);
+ testPass.setBindGroup(0, testBindGroup);
+ testPass.draw(3);
+ testPass.end();
+
+ // Check that the contents of the textures are what we expect. See the shader module for the
+ // computation of what's expected, it writes a 1 on success, 0 otherwise.
+ const checkPipeline = t.device.createRenderPipeline({
+ label: 'checkPipeline',
+ layout: 'auto',
+ vertex: { module: testAndCheckModule },
+ fragment: {
+ module: testAndCheckModule,
+ entryPoint: 'check_texture',
+ targets: [{ format: 'r32uint' }],
+ },
+ primitive: { topology: 'triangle-list' },
+ });
+ const checkBindGroup = t.device.createBindGroup({
+ layout: checkPipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: hasDepth ? ds.createView({ aspect: 'depth-only' }) : fakeDepth.createView(),
+ },
+ {
+ binding: 1,
+ resource: hasStencil
+ ? ds.createView({ aspect: 'stencil-only' })
+ : fakeStencil.createView(),
+ },
+ ],
+ });
+
+ const resultTexture = t.device.createTexture({
+ label: 'resultTexture',
+ format: 'r32uint',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ size: [3, 3],
+ });
+ const checkPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: resultTexture.createView(),
+ loadOp: 'clear',
+ clearValue: [0, 0, 0, 0],
+ storeOp: 'store',
+ },
+ ],
+ });
+ checkPass.setPipeline(checkPipeline);
+ checkPass.setBindGroup(0, checkBindGroup);
+ checkPass.draw(3);
+ checkPass.end();
+
+ t.queue.submit([encoder.finish()]);
+
+ // The check texture should be full of success (a.k.a. 1)!
+ t.expectSingleColor(resultTexture, resultTexture.format, { size: [3, 3, 1], exp: { R: 1 } });
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/reflection.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/reflection.spec.ts
index e9f7b9726c..7fb1a230cc 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/reflection.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/reflection.spec.ts
@@ -8,26 +8,38 @@ import { GPUTest } from '../../gpu_test.js';
export const g = makeTestGroup(GPUTest);
+function* extractValuePropertyKeys(obj: { [k: string]: unknown }) {
+ for (const key in obj) {
+ if (typeof obj[key] !== 'function') {
+ yield key;
+ }
+ }
+}
+
+const kBufferSubcases: readonly {
+ size: number;
+ usage: GPUBufferUsageFlags;
+ label?: string;
+ invalid?: boolean;
+}[] = [
+ { size: 4, usage: GPUConst.BufferUsage.VERTEX },
+ {
+ size: 16,
+ usage:
+ GPUConst.BufferUsage.STORAGE | GPUConst.BufferUsage.COPY_SRC | GPUConst.BufferUsage.UNIFORM,
+ },
+ { size: 32, usage: GPUConst.BufferUsage.MAP_READ | GPUConst.BufferUsage.COPY_DST },
+ { size: 40, usage: GPUConst.BufferUsage.INDEX, label: 'some label' },
+ {
+ size: 32,
+ usage: GPUConst.BufferUsage.MAP_READ | GPUConst.BufferUsage.MAP_WRITE,
+ invalid: true,
+ },
+] as const;
+
g.test('buffer_reflection_attributes')
.desc(`For every buffer attribute, the corresponding descriptor value is carried over.`)
- .paramsSubcasesOnly(u =>
- u.combine('descriptor', [
- { size: 4, usage: GPUConst.BufferUsage.VERTEX },
- {
- size: 16,
- usage:
- GPUConst.BufferUsage.STORAGE |
- GPUConst.BufferUsage.COPY_SRC |
- GPUConst.BufferUsage.UNIFORM,
- },
- { size: 32, usage: GPUConst.BufferUsage.MAP_READ | GPUConst.BufferUsage.COPY_DST },
- {
- size: 32,
- usage: GPUConst.BufferUsage.MAP_READ | GPUConst.BufferUsage.MAP_WRITE,
- invalid: true,
- },
- ] as const)
- )
+ .paramsSubcasesOnly(u => u.combine('descriptor', kBufferSubcases))
.fn(t => {
const { descriptor } = t.params;
@@ -39,53 +51,102 @@ g.test('buffer_reflection_attributes')
}, descriptor.invalid === true);
});
-g.test('texture_reflection_attributes')
- .desc(`For every texture attribute, the corresponding descriptor value is carried over.`)
+g.test('buffer_creation_from_reflection')
+ .desc(
+ `
+ Check that you can create a buffer from a buffer's reflection.
+ This check is to insure that as WebGPU develops this path doesn't
+ suddenly break because of new reflection.
+ `
+ )
.paramsSubcasesOnly(u =>
- u.combine('descriptor', [
- {
- size: { width: 4, height: 4 },
- format: 'rgba8unorm',
- usage: GPUConst.TextureUsage.TEXTURE_BINDING,
- },
- {
- size: { width: 8, height: 8, depthOrArrayLayers: 8 },
- format: 'bgra8unorm',
- usage: GPUConst.TextureUsage.RENDER_ATTACHMENT | GPUConst.TextureUsage.COPY_SRC,
- },
- {
- size: [4, 4],
- format: 'rgba8unorm',
- usage: GPUConst.TextureUsage.TEXTURE_BINDING,
- mipLevelCount: 2,
- },
- {
- size: [16, 16, 16],
- format: 'rgba8unorm',
- usage: GPUConst.TextureUsage.TEXTURE_BINDING,
- dimension: '3d',
- },
- {
- size: [32],
- format: 'rgba8unorm',
- usage: GPUConst.TextureUsage.TEXTURE_BINDING,
- dimension: '1d',
- },
- {
- size: { width: 4, height: 4 },
- format: 'rgba8unorm',
- usage: GPUConst.TextureUsage.RENDER_ATTACHMENT,
- sampleCount: 4,
- },
- {
- size: { width: 4, height: 4 },
- format: 'rgba8unorm',
- usage: GPUConst.TextureUsage.TEXTURE_BINDING,
- sampleCount: 4,
- invalid: true,
- },
- ] as const)
+ u.combine('descriptor', kBufferSubcases).filter(p => !p.descriptor.invalid)
)
+
+ .fn(t => {
+ const { descriptor } = t.params;
+
+ const buffer = t.device.createBuffer(descriptor);
+ t.trackForCleanup(buffer);
+ const buffer2 = t.device.createBuffer(buffer);
+ t.trackForCleanup(buffer2);
+
+ const bufferAsObject = buffer as unknown as { [k: string]: unknown };
+ const buffer2AsObject = buffer2 as unknown as { [k: string]: unknown };
+ const keys = [...extractValuePropertyKeys(bufferAsObject)];
+
+ // Sanity check
+ t.expect(keys.includes('size'));
+ t.expect(keys.includes('usage'));
+ t.expect(keys.includes('label'));
+
+ for (const key of keys) {
+ t.expect(bufferAsObject[key] === buffer2AsObject[key], key);
+ }
+ });
+
+const kTextureSubcases: readonly {
+ size: GPUExtent3D;
+ format: GPUTextureFormat;
+ usage: GPUTextureUsageFlags;
+ mipLevelCount?: number;
+ label?: string;
+ dimension?: GPUTextureDimension;
+ sampleCount?: number;
+ invalid?: boolean;
+}[] = [
+ {
+ size: { width: 4, height: 4 },
+ format: 'rgba8unorm',
+ usage: GPUConst.TextureUsage.TEXTURE_BINDING,
+ },
+ {
+ size: { width: 4, height: 4 },
+ format: 'rgba8unorm',
+ usage: GPUConst.TextureUsage.TEXTURE_BINDING,
+ label: 'some label',
+ },
+ {
+ size: { width: 8, height: 8, depthOrArrayLayers: 8 },
+ format: 'bgra8unorm',
+ usage: GPUConst.TextureUsage.RENDER_ATTACHMENT | GPUConst.TextureUsage.COPY_SRC,
+ },
+ {
+ size: [4, 4],
+ format: 'rgba8unorm',
+ usage: GPUConst.TextureUsage.TEXTURE_BINDING,
+ mipLevelCount: 2,
+ },
+ {
+ size: [16, 16, 16],
+ format: 'rgba8unorm',
+ usage: GPUConst.TextureUsage.TEXTURE_BINDING,
+ dimension: '3d',
+ },
+ {
+ size: [32],
+ format: 'rgba8unorm',
+ usage: GPUConst.TextureUsage.TEXTURE_BINDING,
+ dimension: '1d',
+ },
+ {
+ size: { width: 4, height: 4 },
+ format: 'rgba8unorm',
+ usage: GPUConst.TextureUsage.RENDER_ATTACHMENT,
+ sampleCount: 4,
+ },
+ {
+ size: { width: 4, height: 4 },
+ format: 'rgba8unorm',
+ usage: GPUConst.TextureUsage.TEXTURE_BINDING,
+ sampleCount: 4,
+ invalid: true,
+ },
+] as const;
+
+g.test('texture_reflection_attributes')
+ .desc(`For every texture attribute, the corresponding descriptor value is carried over.`)
+ .paramsSubcasesOnly(u => u.combine('descriptor', kTextureSubcases))
.fn(t => {
const { descriptor } = t.params;
@@ -116,18 +177,77 @@ g.test('texture_reflection_attributes')
}, descriptor.invalid === true);
});
-g.test('query_set_reflection_attributes')
- .desc(`For every queue attribute, the corresponding descriptor value is carried over.`)
+interface TextureWithSize extends GPUTexture {
+ size: GPUExtent3D;
+}
+
+g.test('texture_creation_from_reflection')
+ .desc(
+ `
+ Check that you can create a texture from a texture's reflection.
+ This check is to insure that as WebGPU develops this path doesn't
+ suddenly break because of new reflection.
+ `
+ )
.paramsSubcasesOnly(u =>
- u.combine('descriptor', [
- { type: 'occlusion', count: 4 },
- { type: 'occlusion', count: 16 },
- { type: 'occlusion', count: 8193, invalid: true },
- ] as const)
+ u.combine('descriptor', kTextureSubcases).filter(p => !p.descriptor.invalid)
)
.fn(t => {
const { descriptor } = t.params;
+ const texture = t.device.createTexture(descriptor);
+ t.trackForCleanup(texture);
+ const textureWithSize = texture as TextureWithSize;
+ textureWithSize.size = [texture.width, texture.height, texture.depthOrArrayLayers];
+ const texture2 = t.device.createTexture(textureWithSize);
+ t.trackForCleanup(texture2);
+
+ const textureAsObject = texture as unknown as { [k: string]: unknown };
+ const texture2AsObject = texture2 as unknown as { [k: string]: unknown };
+ const keys = [...extractValuePropertyKeys(textureAsObject)].filter(k => k !== 'size');
+
+ // Sanity check
+ t.expect(keys.includes('format'));
+ t.expect(keys.includes('usage'));
+ t.expect(keys.includes('label'));
+
+ for (const key of keys) {
+ t.expect(textureAsObject[key] === texture2AsObject[key], key);
+ }
+
+ // MAINTENANCE_TODO: Check this if it is made possible by a spec change.
+ //
+ // texture3 = t.device.createTexture({
+ // ...texture,
+ // size: [texture.width, texture.height, texture.depthOrArrayLayers],
+ // });
+ //
+ // and this
+ //
+ // texture3 = t.device.createTexture({
+ // size: [texture.width, texture.height, texture.depthOrArrayLayers],
+ // ...texture,
+ // });
+ });
+
+const kQuerySetSubcases: readonly {
+ type: GPUQueryType;
+ count: number;
+ label?: string;
+ invalid?: boolean;
+}[] = [
+ { type: 'occlusion', count: 4 },
+ { type: 'occlusion', count: 16 },
+ { type: 'occlusion', count: 32, label: 'some label' },
+ { type: 'occlusion', count: 8193, invalid: true },
+] as const;
+
+g.test('query_set_reflection_attributes')
+ .desc(`For every queue attribute, the corresponding descriptor value is carried over.`)
+ .paramsSubcasesOnly(u => u.combine('descriptor', kQuerySetSubcases))
+ .fn(t => {
+ const { descriptor } = t.params;
+
t.expectValidationError(() => {
const querySet = t.device.createQuerySet(descriptor);
@@ -135,3 +255,36 @@ g.test('query_set_reflection_attributes')
t.expect(querySet.count === descriptor.count);
}, descriptor.invalid === true);
});
+
+g.test('query_set_creation_from_reflection')
+ .desc(
+ `
+ Check that you can create a queryset from a queryset's reflection.
+ This check is to insure that as WebGPU develops this path doesn't
+ suddenly break because of new reflection.
+ `
+ )
+ .paramsSubcasesOnly(u =>
+ u.combine('descriptor', kQuerySetSubcases).filter(p => !p.descriptor.invalid)
+ )
+ .fn(t => {
+ const { descriptor } = t.params;
+
+ const querySet = t.device.createQuerySet(descriptor);
+ t.trackForCleanup(querySet);
+ const querySet2 = t.device.createQuerySet(querySet);
+ t.trackForCleanup(querySet2);
+
+ const querySetAsObject = querySet as unknown as { [k: string]: unknown };
+ const querySet2AsObject = querySet2 as unknown as { [k: string]: unknown };
+ const keys = [...extractValuePropertyKeys(querySetAsObject)];
+
+ // Sanity check
+ t.expect(keys.includes('type'));
+ t.expect(keys.includes('count'));
+ t.expect(keys.includes('label'));
+
+ for (const key of keys) {
+ t.expect(querySetAsObject[key] === querySet2AsObject[key], key);
+ }
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts
index 00069b777f..b28e1b381c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts
@@ -6,6 +6,8 @@ Also tested:
- The positions of samples in the standard sample patterns.
- Per-sample interpolation sampling: @interpolate(perspective, sample).
+TODO: Test sample_mask as an input.
+
TODO: add a test without a 0th color attachment (sparse color attachment), with different color attachments and alpha value output.
The cross-platform behavior is unknown. could be any of:
- coverage is always 100%
@@ -19,7 +21,7 @@ import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { assert, range } from '../../../../common/util/util.js';
import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
import { checkElementsPassPredicate, checkElementsEqual } from '../../../util/check_contents.js';
-import { TypeF32, TypeU32 } from '../../../util/conversion.js';
+import { Type } from '../../../util/conversion.js';
import { TexelView } from '../../../util/texture/texel_view.js';
const kColors = [
@@ -435,8 +437,8 @@ class F extends TextureTestMixin(GPUTest) {
sampleMask: number,
fragmentShaderOutputMask: number
) {
- const buffer = this.copySinglePixelTextureToBufferUsingComputePass(
- TypeF32, // correspond to 'rgba8unorm' format
+ const buffer = this.copy2DTextureToBufferUsingComputePass(
+ Type.f32, // correspond to 'rgba8unorm' format
4,
texture.createView(),
sampleCount
@@ -459,10 +461,10 @@ class F extends TextureTestMixin(GPUTest) {
sampleMask: number,
fragmentShaderOutputMask: number
) {
- const buffer = this.copySinglePixelTextureToBufferUsingComputePass(
+ const buffer = this.copy2DTextureToBufferUsingComputePass(
// Use f32 as the scalar type for depth (depth24plus, depth32float)
// Use u32 as the scalar type for stencil (stencil8)
- aspect === 'depth-only' ? TypeF32 : TypeU32,
+ aspect === 'depth-only' ? Type.f32 : Type.u32,
1,
depthStencilTexture.createView({ aspect }),
sampleCount
@@ -702,8 +704,8 @@ color' <= color.
2
);
- const colorBuffer = t.copySinglePixelTextureToBufferUsingComputePass(
- TypeF32, // correspond to 'rgba8unorm' format
+ const colorBuffer = t.copy2DTextureToBufferUsingComputePass(
+ Type.f32, // correspond to 'rgba8unorm' format
4,
color.createView(),
sampleCount
@@ -714,8 +716,8 @@ color' <= color.
});
colorResultPromises.push(colorResult);
- const depthBuffer = t.copySinglePixelTextureToBufferUsingComputePass(
- TypeF32, // correspond to 'depth24plus-stencil8' format
+ const depthBuffer = t.copy2DTextureToBufferUsingComputePass(
+ Type.f32, // correspond to 'depth24plus-stencil8' format
1,
depthStencil.createView({ aspect: 'depth-only' }),
sampleCount
@@ -726,8 +728,8 @@ color' <= color.
});
depthResultPromises.push(depthResult);
- const stencilBuffer = t.copySinglePixelTextureToBufferUsingComputePass(
- TypeU32, // correspond to 'depth24plus-stencil8' format
+ const stencilBuffer = t.copy2DTextureToBufferUsingComputePass(
+ Type.u32, // correspond to 'depth24plus-stencil8' format
1,
depthStencil.createView({ aspect: 'stencil-only' }),
sampleCount
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/3d_texture_slices.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/3d_texture_slices.spec.ts
new file mode 100644
index 0000000000..98f51d3dff
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/3d_texture_slices.spec.ts
@@ -0,0 +1,363 @@
+export const description = `
+Test rendering to 3d texture slices.
+- Render to same slice on different render pass, different textures, or texture [1, 1, N]'s different mip levels
+- Render to different slices at mip levels on same texture in render pass
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kTextureFormatInfo } from '../../../format_info.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { kBytesPerRowAlignment } from '../../../util/texture/layout.js';
+
+const kSize = 4;
+const kFormat = 'rgba8unorm' as const;
+
+class F extends GPUTest {
+ createShaderModule(attachmentCount: number = 1): GPUShaderModule {
+ let locations = '';
+ let outputs = '';
+ for (let i = 0; i < attachmentCount; i++) {
+ locations = locations + `@location(${i}) color${i} : vec4f, \n`;
+ outputs = outputs + `output.color${i} = vec4f(0.0, 1.0, 0.0, 1.0);\n`;
+ }
+
+ return this.device.createShaderModule({
+ code: `
+ struct Output {
+ ${locations}
+ }
+
+ @vertex
+ fn main_vs(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
+ var pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
+ // Triangle is slightly extended so its edge doesn't cut through pixel centers.
+ vec2<f32>(-1.0, 1.01),
+ vec2<f32>(1.01, -1.0),
+ vec2<f32>(-1.0, -1.0));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }
+
+ @fragment
+ fn main_fs() -> Output {
+ var output : Output;
+ ${outputs}
+ return output;
+ }
+ `,
+ });
+ }
+
+ getBufferSizeAndOffset(
+ attachmentWidth: number,
+ attachmentHeight: number,
+ attachmentCount: number
+ ): { bufferSize: number; bufferOffset: number } {
+ const bufferSize =
+ (attachmentCount * attachmentHeight - 1) * kBytesPerRowAlignment + attachmentWidth * 4;
+ const bufferOffset = attachmentCount > 1 ? attachmentHeight * kBytesPerRowAlignment : 0;
+ return { bufferSize, bufferOffset };
+ }
+
+ checkAttachmentResult(
+ attachmentWidth: number,
+ attachmentHeight: number,
+ attachmentCount: number,
+ buffer: GPUBuffer
+ ) {
+ const { bufferSize, bufferOffset } = this.getBufferSizeAndOffset(
+ attachmentWidth,
+ attachmentHeight,
+ attachmentCount
+ );
+ const expectedData = new Uint8Array(bufferSize);
+ for (let i = 0; i < attachmentCount; i++) {
+ for (let j = 0; j < attachmentHeight; j++) {
+ for (let k = 0; k < attachmentWidth; k++) {
+ expectedData[i * bufferOffset + j * 256 + k * 4] = k <= j ? 0x00 : 0xff;
+ expectedData[i * bufferOffset + j * 256 + k * 4 + 1] = k <= j ? 0xff : 0x00;
+ expectedData[i * bufferOffset + j * 256 + k * 4 + 2] = 0x00;
+ expectedData[i * bufferOffset + j * 256 + k * 4 + 3] = 0xff;
+ }
+ }
+ }
+
+ this.expectGPUBufferValuesEqual(buffer, expectedData);
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('one_color_attachment,mip_levels')
+ .desc(
+ `
+ Render to a 3d texture slice with mip levels.
+ `
+ )
+ .params(u => u.combine('mipLevel', [0, 1, 2]).combine('depthSlice', [0, 1]))
+ .fn(t => {
+ const { mipLevel, depthSlice } = t.params;
+
+ const texture = t.device.createTexture({
+ size: [kSize << mipLevel, kSize << mipLevel, 2 << mipLevel],
+ dimension: '3d',
+ format: kFormat,
+ mipLevelCount: mipLevel + 1,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ });
+
+ const { bufferSize } = t.getBufferSizeAndOffset(kSize, kSize, 1);
+
+ const buffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+ });
+
+ const module = t.createShaderModule();
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module },
+ fragment: {
+ module,
+ targets: [{ format: kFormat }],
+ },
+ primitive: { topology: 'triangle-list' },
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: texture.createView({
+ baseMipLevel: mipLevel,
+ mipLevelCount: 1,
+ }),
+ depthSlice,
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store',
+ },
+ ],
+ });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+ encoder.copyTextureToBuffer(
+ { texture, mipLevel, origin: { x: 0, y: 0, z: depthSlice } },
+ { buffer, bytesPerRow: 256 },
+ { width: kSize, height: kSize, depthOrArrayLayers: 1 }
+ );
+ t.device.queue.submit([encoder.finish()]);
+
+ t.checkAttachmentResult(kSize, kSize, 1, buffer);
+ });
+
+g.test('multiple_color_attachments,same_mip_level')
+ .desc(
+ `
+ Render to the different slices of 3d texture in multiple color attachments.
+ - Same 3d texture with different slices at same mip level
+ - Different 3d textures with same slice at same mip level
+ `
+ )
+ .params(u =>
+ u
+ .combine('sameTexture', [true, false])
+ .beginSubcases()
+ .combine('samePass', [true, false])
+ .combine('mipLevel', [0, 1])
+ )
+ .fn(t => {
+ const { sameTexture, samePass, mipLevel } = t.params;
+
+ const formatByteCost = kTextureFormatInfo[kFormat].colorRender.byteCost;
+ const maxAttachmentCountPerSample = Math.trunc(
+ t.device.limits.maxColorAttachmentBytesPerSample / formatByteCost
+ );
+ const attachmentCount = Math.min(
+ maxAttachmentCountPerSample,
+ t.device.limits.maxColorAttachments
+ );
+
+ const descriptor = {
+ size: [kSize << mipLevel, kSize << mipLevel, (1 << attachmentCount) << mipLevel],
+ dimension: '3d',
+ format: kFormat,
+ mipLevelCount: mipLevel + 1,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ } as const;
+
+ const texture = t.device.createTexture(descriptor);
+
+ const textures: GPUTexture[] = [];
+ const colorAttachments: GPURenderPassColorAttachment[] = [];
+ for (let i = 0; i < attachmentCount; i++) {
+ if (sameTexture) {
+ textures.push(texture);
+ } else {
+ const diffTexture = t.device.createTexture(descriptor);
+ textures.push(diffTexture);
+ }
+
+ const colorAttachment = {
+ view: textures[i].createView({
+ baseMipLevel: mipLevel,
+ mipLevelCount: 1,
+ }),
+ depthSlice: sameTexture ? i : 0,
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store',
+ } as const;
+
+ colorAttachments.push(colorAttachment);
+ }
+
+ const encoder = t.device.createCommandEncoder();
+
+ if (samePass) {
+ const module = t.createShaderModule(attachmentCount);
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module },
+ fragment: {
+ module,
+ targets: new Array<GPUColorTargetState>(attachmentCount).fill({ format: kFormat }),
+ },
+ primitive: { topology: 'triangle-list' },
+ });
+
+ const pass = encoder.beginRenderPass({ colorAttachments });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+ } else {
+ const module = t.createShaderModule();
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module },
+ fragment: {
+ module,
+ targets: [{ format: kFormat }],
+ },
+ primitive: { topology: 'triangle-list' },
+ });
+
+ for (let i = 0; i < attachmentCount; i++) {
+ const pass = encoder.beginRenderPass({ colorAttachments: [colorAttachments[i]] });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+ }
+ }
+
+ const { bufferSize, bufferOffset } = t.getBufferSizeAndOffset(kSize, kSize, attachmentCount);
+ const buffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+ });
+ for (let i = 0; i < attachmentCount; i++) {
+ encoder.copyTextureToBuffer(
+ {
+ texture: textures[i],
+ mipLevel,
+ origin: { x: 0, y: 0, z: sameTexture ? i : 0 },
+ },
+ { buffer, bytesPerRow: 256, offset: bufferOffset * i },
+ { width: kSize, height: kSize, depthOrArrayLayers: 1 }
+ );
+ }
+
+ t.device.queue.submit([encoder.finish()]);
+
+ t.checkAttachmentResult(kSize, kSize, attachmentCount, buffer);
+ });
+
+g.test('multiple_color_attachments,same_slice_with_diff_mip_levels')
+ .desc(
+ `
+ Render to the same slice of a 3d texture at different mip levels in multiple color attachments.
+ - For texture size with 1x1xN, the same depth slice of different mip levels can be rendered.
+ `
+ )
+ .params(u => u.combine('depthSlice', [0, 1]))
+ .fn(t => {
+ const { depthSlice } = t.params;
+
+ const kBaseSize = 1;
+
+ const formatByteCost = kTextureFormatInfo[kFormat].colorRender.byteCost;
+ const maxAttachmentCountPerSample = Math.trunc(
+ t.device.limits.maxColorAttachmentBytesPerSample / formatByteCost
+ );
+ const attachmentCount = Math.min(
+ maxAttachmentCountPerSample,
+ t.device.limits.maxColorAttachments
+ );
+
+ const module = t.createShaderModule(attachmentCount);
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module },
+ fragment: {
+ module,
+ targets: new Array<GPUColorTargetState>(attachmentCount).fill({ format: kFormat }),
+ },
+ primitive: { topology: 'triangle-list' },
+ });
+
+ const texture = t.device.createTexture({
+ size: [kBaseSize, kBaseSize, (depthSlice + 1) << attachmentCount],
+ dimension: '3d',
+ format: kFormat,
+ mipLevelCount: attachmentCount,
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ });
+
+ const colorAttachments: GPURenderPassColorAttachment[] = [];
+ for (let i = 0; i < attachmentCount; i++) {
+ const colorAttachment = {
+ view: texture.createView({
+ baseMipLevel: i,
+ mipLevelCount: 1,
+ }),
+ depthSlice,
+ clearValue: { r: 1.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store',
+ } as const;
+
+ colorAttachments.push(colorAttachment);
+ }
+
+ const encoder = t.device.createCommandEncoder();
+
+ const pass = encoder.beginRenderPass({ colorAttachments });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+
+ const { bufferSize, bufferOffset } = t.getBufferSizeAndOffset(
+ kBaseSize,
+ kBaseSize,
+ attachmentCount
+ );
+ const buffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+ });
+ for (let i = 0; i < attachmentCount; i++) {
+ encoder.copyTextureToBuffer(
+ { texture, mipLevel: i, origin: { x: 0, y: 0, z: depthSlice } },
+ { buffer, bytesPerRow: 256, offset: bufferOffset * i },
+ { width: kBaseSize, height: kBaseSize, depthOrArrayLayers: 1 }
+ );
+ }
+
+ t.device.queue.submit([encoder.finish()]);
+
+ t.checkAttachmentResult(kBaseSize, kBaseSize, attachmentCount, buffer);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/color_target_state.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/color_target_state.spec.ts
index 1290c6bc99..673e33c2ea 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/color_target_state.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/color_target_state.spec.ts
@@ -11,7 +11,7 @@ import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { assert, TypedArrayBufferView, unreachable } from '../../../../common/util/util.js';
import { kBlendFactors, kBlendOperations } from '../../../capability_info.js';
import { GPUConst } from '../../../constants.js';
-import { kEncodableTextureFormats, kTextureFormatInfo } from '../../../format_info.js';
+import { kRegularTextureFormats, kTextureFormatInfo } from '../../../format_info.js';
import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
import { clamp } from '../../../util/math.js';
import { TexelView } from '../../../util/texture/texel_view.js';
@@ -165,6 +165,7 @@ g.test('blending,GPUBlendComponent')
.combine('component', ['color', 'alpha'] as const)
.combine('srcFactor', kBlendFactors)
.combine('dstFactor', kBlendFactors)
+ .beginSubcases()
.combine('operation', kBlendOperations)
.filter(t => {
if (t.operation === 'min' || t.operation === 'max') {
@@ -172,7 +173,6 @@ g.test('blending,GPUBlendComponent')
}
return true;
})
- .beginSubcases()
.combine('srcColor', [{ r: 0.11, g: 0.61, b: 0.81, a: 0.44 }])
.combine('dstColor', [
{ r: 0.51, g: 0.22, b: 0.71, a: 0.33 },
@@ -318,9 +318,9 @@ struct Uniform {
);
});
-const kBlendableFormats = kEncodableTextureFormats.filter(f => {
+const kBlendableFormats = kRegularTextureFormats.filter(f => {
const info = kTextureFormatInfo[f];
- return info.renderable && info.sampleType === 'float';
+ return info.colorRender && info.color.type === 'float';
});
g.test('blending,formats')
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth.spec.ts
index 3b2227db98..a81a7c7812 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth.spec.ts
@@ -467,13 +467,8 @@ g.test('reverse_depth')
@vertex fn main(
@builtin(vertex_index) VertexIndex : u32,
@builtin(instance_index) InstanceIndex : u32) -> Output {
- // TODO: remove workaround for Tint unary array access broke
- var zv : array<vec2<f32>, 4> = array<vec2<f32>, 4>(
- vec2<f32>(0.2, 0.2),
- vec2<f32>(0.3, 0.3),
- vec2<f32>(-0.1, -0.1),
- vec2<f32>(1.1, 1.1));
- let z : f32 = zv[InstanceIndex].x;
+ let zv = array(0.2, 0.3, -0.1, 1.1);
+ let z = zv[InstanceIndex];
var output : Output;
output.Position = vec4<f32>(0.5, 0.5, z, 1.0);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_bias.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_bias.spec.ts
index 03caff3b25..17e80c5da9 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_bias.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_bias.spec.ts
@@ -304,6 +304,12 @@ g.test('depth_bias')
},
] as const)
)
+ .beforeAllSubcases(t => {
+ t.skipIf(
+ t.isCompatibility && t.params.biasClamp !== 0,
+ 'non zero depthBiasClamp is not supported in compatibility mode'
+ );
+ })
.fn(t => {
t.runDepthBiasTest('depth32float', t.params);
});
@@ -346,6 +352,12 @@ g.test('depth_bias_24bit_format')
},
] as const)
)
+ .beforeAllSubcases(t => {
+ t.skipIf(
+ t.isCompatibility && t.params.biasClamp !== 0,
+ 'non zero depthBiasClamp is not supported in compatibility mode'
+ );
+ })
.fn(t => {
const { format } = t.params;
t.runDepthBiasTestFor24BitFormat(format, t.params);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_clip_clamp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_clip_clamp.spec.ts
index 65e2e8af1f..1d3b6d8b7a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_clip_clamp.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth_clip_clamp.spec.ts
@@ -4,6 +4,7 @@ depth ranges as well.
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { assert } from '../../../../common/util/util.js';
import { kDepthStencilFormats, kTextureFormatInfo } from '../../../format_info.js';
import { GPUTest } from '../../../gpu_test.js';
import {
@@ -52,6 +53,7 @@ have unexpected values then get drawn to the color buffer, which is later checke
.fn(async t => {
const { format, unclippedDepth, writeDepth, multisampled } = t.params;
const info = kTextureFormatInfo[format];
+ assert(!!info.depth);
/** Number of depth values to test for both vertex output and frag_depth output. */
const kNumDepthValues = 8;
@@ -222,16 +224,16 @@ have unexpected values then get drawn to the color buffer, which is later checke
: undefined;
const dsActual =
- !multisampled && info.bytesPerBlock
+ !multisampled && info.depth.bytes
? t.device.createBuffer({
- size: kNumTestPoints * info.bytesPerBlock,
+ size: kNumTestPoints * info.depth.bytes,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
})
: undefined;
const dsExpected =
- !multisampled && info.bytesPerBlock
+ !multisampled && info.depth.bytes
? t.device.createBuffer({
- size: kNumTestPoints * info.bytesPerBlock,
+ size: kNumTestPoints * info.depth.bytes,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
})
: undefined;
@@ -270,7 +272,9 @@ have unexpected values then get drawn to the color buffer, which is later checke
pass.end();
}
if (dsActual) {
- enc.copyTextureToBuffer({ texture: dsTexture }, { buffer: dsActual }, [kNumTestPoints]);
+ enc.copyTextureToBuffer({ texture: dsTexture, aspect: 'depth-only' }, { buffer: dsActual }, [
+ kNumTestPoints,
+ ]);
}
{
const clearValue = [0, 0, 0, 0]; // Will see this color if the check passed.
@@ -302,7 +306,11 @@ have unexpected values then get drawn to the color buffer, which is later checke
}
enc.copyTextureToBuffer({ texture: checkTexture }, { buffer: checkBuffer }, [kNumTestPoints]);
if (dsExpected) {
- enc.copyTextureToBuffer({ texture: dsTexture }, { buffer: dsExpected }, [kNumTestPoints]);
+ enc.copyTextureToBuffer(
+ { texture: dsTexture, aspect: 'depth-only' },
+ { buffer: dsExpected },
+ [kNumTestPoints]
+ );
}
t.device.queue.submit([enc.finish()]);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_copy.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_copy.ts
index 2a4ca5e6a4..546db16c7e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_copy.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_copy.ts
@@ -1,7 +1,8 @@
import { assert } from '../../../../../common/util/util.js';
import { kTextureFormatInfo, EncodableTextureFormat } from '../../../../format_info.js';
import { virtualMipSize } from '../../../../util/texture/base.js';
-import { CheckContents } from '../texture_zero.spec.js';
+
+import { CheckContents } from './texture_zero_init_test.js';
export const checkContentsByBufferCopy: CheckContents = (
t,
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_ds_test.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_ds_test.ts
index 8646062452..72ef2909f2 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_ds_test.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_ds_test.ts
@@ -2,7 +2,8 @@ import { assert } from '../../../../../common/util/util.js';
import { kTextureFormatInfo } from '../../../../format_info.js';
import { GPUTest } from '../../../../gpu_test.js';
import { virtualMipSize } from '../../../../util/texture/base.js';
-import { CheckContents } from '../texture_zero.spec.js';
+
+import { CheckContents } from './texture_zero_init_test.js';
function makeFullscreenVertexModule(device: GPUDevice) {
return device.createShaderModule({
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_sampling.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_sampling.ts
index 64b4f73b34..fd268be3b9 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_sampling.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/by_sampling.ts
@@ -6,7 +6,8 @@ import {
getSingleDataType,
getComponentReadbackTraits,
} from '../../../../util/texture/texel_data.js';
-import { CheckContents } from '../texture_zero.spec.js';
+
+import { CheckContents } from './texture_zero_init_test.js';
export const checkContentsBySampling: CheckContents = (
t,
@@ -41,14 +42,20 @@ export const checkContentsBySampling: CheckContents = (
? componentOrder[0].toLowerCase()
: componentOrder.map(c => c.toLowerCase()).join('') + '[i]';
- const _xd = '_' + params.dimension;
+ const viewDimension =
+ t.isCompatibility && params.dimension === '2d' && texture.depthOrArrayLayers > 1
+ ? '2d-array'
+ : params.dimension;
+ const _xd = `_${viewDimension.replace('-', '_')}`;
const _multisampled = params.sampleCount > 1 ? '_multisampled' : '';
const texelIndexExpression =
- params.dimension === '2d'
+ viewDimension === '2d'
? 'vec2<i32>(GlobalInvocationID.xy)'
- : params.dimension === '3d'
+ : viewDimension === '2d-array'
+ ? 'vec2<i32>(GlobalInvocationID.xy), constants.layer'
+ : viewDimension === '3d'
? 'vec3<i32>(GlobalInvocationID.xyz)'
- : params.dimension === '1d'
+ : viewDimension === '1d'
? 'i32(GlobalInvocationID.x)'
: unreachable();
const computePipeline = t.device.createComputePipeline({
@@ -58,7 +65,8 @@ export const checkContentsBySampling: CheckContents = (
module: t.device.createShaderModule({
code: `
struct Constants {
- level : i32
+ level : i32,
+ layer : i32,
};
@group(0) @binding(0) var<uniform> constants : Constants;
@@ -90,10 +98,10 @@ export const checkContentsBySampling: CheckContents = (
for (const layer of layers) {
const ubo = t.device.createBuffer({
mappedAtCreation: true,
- size: 4,
+ size: 8,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
- new Int32Array(ubo.getMappedRange(), 0, 1)[0] = level;
+ new Int32Array(ubo.getMappedRange()).set([level, layer]);
ubo.unmap();
const byteLength =
@@ -104,6 +112,14 @@ export const checkContentsBySampling: CheckContents = (
});
t.trackForCleanup(resultBuffer);
+ const viewDescriptor: GPUTextureViewDescriptor = {
+ ...(!t.isCompatibility && {
+ baseArrayLayer: layer,
+ arrayLayerCount: 1,
+ }),
+ dimension: viewDimension,
+ };
+
const bindGroup = t.device.createBindGroup({
layout: computePipeline.getBindGroupLayout(0),
entries: [
@@ -113,11 +129,7 @@ export const checkContentsBySampling: CheckContents = (
},
{
binding: 1,
- resource: texture.createView({
- baseArrayLayer: layer,
- arrayLayerCount: 1,
- dimension: params.dimension,
- }),
+ resource: texture.createView(viewDescriptor),
},
{
binding: 3,
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/texture_zero_init_test.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/texture_zero_init_test.ts
new file mode 100644
index 0000000000..6ff3ab4c9b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/check_texture/texture_zero_init_test.ts
@@ -0,0 +1,548 @@
+import { TestCaseRecorder, TestParams } from '../../../../../common/framework/fixture.js';
+import {
+ kUnitCaseParamsBuilder,
+ ParamTypeOf,
+} from '../../../../../common/framework/params_builder.js';
+import { assert, unreachable } from '../../../../../common/util/util.js';
+import { kTextureAspects, kTextureDimensions } from '../../../../capability_info.js';
+import { GPUConst } from '../../../../constants.js';
+import {
+ kTextureFormatInfo,
+ kUncompressedTextureFormats,
+ textureDimensionAndFormatCompatible,
+ UncompressedTextureFormat,
+ EncodableTextureFormat,
+} from '../../../../format_info.js';
+import { GPUTest, GPUTestSubcaseBatchState } from '../../../../gpu_test.js';
+import { virtualMipSize } from '../../../../util/texture/base.js';
+import { createTextureUploadBuffer } from '../../../../util/texture/layout.js';
+import { BeginEndRange, SubresourceRange } from '../../../../util/texture/subresource.js';
+import {
+ PerTexelComponent,
+ kTexelRepresentationInfo,
+} from '../../../../util/texture/texel_data.js';
+
+export enum UninitializeMethod {
+ Creation = 'Creation', // The texture was just created. It is uninitialized.
+ StoreOpClear = 'StoreOpClear', // The texture was rendered to with GPUStoreOp "clear"
+}
+const kUninitializeMethods = Object.keys(UninitializeMethod) as UninitializeMethod[];
+
+export const enum ReadMethod {
+ Sample = 'Sample', // The texture is sampled from
+ CopyToBuffer = 'CopyToBuffer', // The texture is copied to a buffer
+ CopyToTexture = 'CopyToTexture', // The texture is copied to another texture
+ DepthTest = 'DepthTest', // The texture is read as a depth buffer
+ StencilTest = 'StencilTest', // The texture is read as a stencil buffer
+ ColorBlending = 'ColorBlending', // Read the texture by blending as a color attachment
+ Storage = 'Storage', // Read the texture as a storage texture
+}
+
+// Test with these mip level counts
+type MipLevels = 1 | 5;
+const kMipLevelCounts: MipLevels[] = [1, 5];
+
+// For each mip level count, define the mip ranges to leave uninitialized.
+const kUninitializedMipRangesToTest: { [k in MipLevels]: BeginEndRange[] } = {
+ 1: [{ begin: 0, end: 1 }], // Test the only mip
+ 5: [
+ { begin: 0, end: 2 },
+ { begin: 3, end: 4 },
+ ], // Test a range and a single mip
+};
+
+// Test with these sample counts.
+const kSampleCounts: number[] = [1, 4];
+
+// Test with these layer counts.
+type LayerCounts = 1 | 7;
+
+// For each layer count, define the layers to leave uninitialized.
+const kUninitializedLayerRangesToTest: { [k in LayerCounts]: BeginEndRange[] } = {
+ 1: [{ begin: 0, end: 1 }], // Test the only layer
+ 7: [
+ { begin: 2, end: 4 },
+ { begin: 6, end: 7 },
+ ], // Test a range and a single layer
+};
+
+// Enums to abstract over color / depth / stencil values in textures. Depending on the texture format,
+// the data for each value may have a different representation. These enums are converted to a
+// representation such that their values can be compared. ex.) An integer is needed to upload to an
+// unsigned normalized format, but its value is read as a float in the shader.
+export const enum InitializedState {
+ Canary, // Set on initialized subresources. It should stay the same. On discarded resources, we should observe zero.
+ Zero, // We check that uninitialized subresources are in this state when read back.
+}
+
+const initializedStateAsFloat = {
+ [InitializedState.Zero]: 0,
+ [InitializedState.Canary]: 1,
+};
+
+const initializedStateAsUint = {
+ [InitializedState.Zero]: 0,
+ [InitializedState.Canary]: 1,
+};
+
+const initializedStateAsSint = {
+ [InitializedState.Zero]: 0,
+ [InitializedState.Canary]: -1,
+};
+
+function initializedStateAsColor(
+ state: InitializedState,
+ format: GPUTextureFormat
+): [number, number, number, number] {
+ let value;
+ if (format.indexOf('uint') !== -1) {
+ value = initializedStateAsUint[state];
+ } else if (format.indexOf('sint') !== -1) {
+ value = initializedStateAsSint[state];
+ } else {
+ value = initializedStateAsFloat[state];
+ }
+ return [value, value, value, value];
+}
+
+const initializedStateAsDepth = {
+ [InitializedState.Zero]: 0,
+ [InitializedState.Canary]: 0.8,
+};
+
+const initializedStateAsStencil = {
+ [InitializedState.Zero]: 0,
+ [InitializedState.Canary]: 42,
+};
+
+function allAspectsCopyDst(info: (typeof kTextureFormatInfo)[UncompressedTextureFormat]) {
+ return (
+ (!info.color || info.color.copyDst) &&
+ (!info.depth || info.depth.copyDst) &&
+ (!info.stencil || info.stencil.copyDst)
+ );
+}
+
+export function getRequiredTextureUsage(
+ format: UncompressedTextureFormat,
+ sampleCount: number,
+ uninitializeMethod: UninitializeMethod,
+ readMethod: ReadMethod
+): GPUTextureUsageFlags {
+ let usage: GPUTextureUsageFlags = GPUConst.TextureUsage.COPY_DST;
+
+ switch (uninitializeMethod) {
+ case UninitializeMethod.Creation:
+ break;
+ case UninitializeMethod.StoreOpClear:
+ usage |= GPUConst.TextureUsage.RENDER_ATTACHMENT;
+ break;
+ default:
+ unreachable();
+ }
+
+ switch (readMethod) {
+ case ReadMethod.CopyToBuffer:
+ case ReadMethod.CopyToTexture:
+ usage |= GPUConst.TextureUsage.COPY_SRC;
+ break;
+ case ReadMethod.Sample:
+ usage |= GPUConst.TextureUsage.TEXTURE_BINDING;
+ break;
+ case ReadMethod.Storage:
+ usage |= GPUConst.TextureUsage.STORAGE_BINDING;
+ break;
+ case ReadMethod.DepthTest:
+ case ReadMethod.StencilTest:
+ case ReadMethod.ColorBlending:
+ usage |= GPUConst.TextureUsage.RENDER_ATTACHMENT;
+ break;
+ default:
+ unreachable();
+ }
+
+ if (sampleCount > 1) {
+ // Copies to multisampled textures are not allowed. We need OutputAttachment to initialize
+ // canary data in multisampled textures.
+ usage |= GPUConst.TextureUsage.RENDER_ATTACHMENT;
+ }
+
+ const info = kTextureFormatInfo[format];
+ if (!allAspectsCopyDst(info)) {
+ // Copies are not possible. We need OutputAttachment to initialize
+ // canary data.
+ if (info.color) assert(!!info.colorRender, 'not implemented for non-renderable color');
+ usage |= GPUConst.TextureUsage.RENDER_ATTACHMENT;
+ }
+
+ return usage;
+}
+
+export class TextureZeroInitTest extends GPUTest {
+ readonly stateToTexelComponents: { [k in InitializedState]: PerTexelComponent<number> };
+
+ private p: TextureZeroParams;
+ constructor(sharedState: GPUTestSubcaseBatchState, rec: TestCaseRecorder, params: TestParams) {
+ super(sharedState, rec, params);
+ this.p = params as TextureZeroParams;
+
+ const stateToTexelComponents = (state: InitializedState) => {
+ const [R, G, B, A] = initializedStateAsColor(state, this.p.format);
+ return {
+ R,
+ G,
+ B,
+ A,
+ Depth: initializedStateAsDepth[state],
+ Stencil: initializedStateAsStencil[state],
+ };
+ };
+
+ this.stateToTexelComponents = {
+ [InitializedState.Zero]: stateToTexelComponents(InitializedState.Zero),
+ [InitializedState.Canary]: stateToTexelComponents(InitializedState.Canary),
+ };
+ }
+
+ get textureWidth(): number {
+ let width = 1 << this.p.mipLevelCount;
+ if (this.p.nonPowerOfTwo) {
+ width = 2 * width - 1;
+ }
+ return width;
+ }
+
+ get textureHeight(): number {
+ if (this.p.dimension === '1d') {
+ return 1;
+ }
+
+ let height = 1 << this.p.mipLevelCount;
+ if (this.p.nonPowerOfTwo) {
+ height = 2 * height - 1;
+ }
+ return height;
+ }
+
+ get textureDepth(): number {
+ return this.p.dimension === '3d' ? 11 : 1;
+ }
+
+ get textureDepthOrArrayLayers(): number {
+ return this.p.dimension === '2d' ? this.p.layerCount : this.textureDepth;
+ }
+
+ // Used to iterate subresources and check that their uninitialized contents are zero when accessed
+ *iterateUninitializedSubresources(): Generator<SubresourceRange> {
+ for (const mipRange of kUninitializedMipRangesToTest[this.p.mipLevelCount]) {
+ for (const layerRange of kUninitializedLayerRangesToTest[this.p.layerCount]) {
+ yield new SubresourceRange({ mipRange, layerRange });
+ }
+ }
+ }
+
+ // Used to iterate and initialize other subresources not checked for zero-initialization.
+ // Zero-initialization of uninitialized subresources should not have side effects on already
+ // initialized subresources.
+ *iterateInitializedSubresources(): Generator<SubresourceRange> {
+ const uninitialized: boolean[][] = new Array(this.p.mipLevelCount);
+ for (let level = 0; level < uninitialized.length; ++level) {
+ uninitialized[level] = new Array(this.p.layerCount);
+ }
+ for (const subresources of this.iterateUninitializedSubresources()) {
+ for (const { level, layer } of subresources.each()) {
+ uninitialized[level][layer] = true;
+ }
+ }
+ for (let level = 0; level < uninitialized.length; ++level) {
+ for (let layer = 0; layer < uninitialized[level].length; ++layer) {
+ if (!uninitialized[level][layer]) {
+ yield new SubresourceRange({
+ mipRange: { begin: level, count: 1 },
+ layerRange: { begin: layer, count: 1 },
+ });
+ }
+ }
+ }
+ }
+
+ *generateTextureViewDescriptorsForRendering(
+ aspect: GPUTextureAspect,
+ subresourceRange?: SubresourceRange
+ ): Generator<GPUTextureViewDescriptor> {
+ const viewDescriptor: GPUTextureViewDescriptor = {
+ dimension: '2d',
+ aspect,
+ };
+
+ if (subresourceRange === undefined) {
+ return viewDescriptor;
+ }
+
+ for (const { level, layer } of subresourceRange.each()) {
+ yield {
+ ...viewDescriptor,
+ baseMipLevel: level,
+ mipLevelCount: 1,
+ baseArrayLayer: layer,
+ arrayLayerCount: 1,
+ };
+ }
+ }
+
+ private initializeWithStoreOp(
+ state: InitializedState,
+ texture: GPUTexture,
+ subresourceRange?: SubresourceRange
+ ): void {
+ const commandEncoder = this.device.createCommandEncoder();
+ commandEncoder.pushDebugGroup('initializeWithStoreOp');
+
+ for (const viewDescriptor of this.generateTextureViewDescriptorsForRendering(
+ 'all',
+ subresourceRange
+ )) {
+ if (kTextureFormatInfo[this.p.format].color) {
+ commandEncoder
+ .beginRenderPass({
+ colorAttachments: [
+ {
+ view: texture.createView(viewDescriptor),
+ storeOp: 'store',
+ clearValue: initializedStateAsColor(state, this.p.format),
+ loadOp: 'clear',
+ },
+ ],
+ })
+ .end();
+ } else {
+ const depthStencilAttachment: GPURenderPassDepthStencilAttachment = {
+ view: texture.createView(viewDescriptor),
+ };
+ if (kTextureFormatInfo[this.p.format].depth) {
+ depthStencilAttachment.depthClearValue = initializedStateAsDepth[state];
+ depthStencilAttachment.depthLoadOp = 'clear';
+ depthStencilAttachment.depthStoreOp = 'store';
+ }
+ if (kTextureFormatInfo[this.p.format].stencil) {
+ depthStencilAttachment.stencilClearValue = initializedStateAsStencil[state];
+ depthStencilAttachment.stencilLoadOp = 'clear';
+ depthStencilAttachment.stencilStoreOp = 'store';
+ }
+ commandEncoder
+ .beginRenderPass({
+ colorAttachments: [],
+ depthStencilAttachment,
+ })
+ .end();
+ }
+ }
+
+ commandEncoder.popDebugGroup();
+ this.queue.submit([commandEncoder.finish()]);
+ }
+
+ private initializeWithCopy(
+ texture: GPUTexture,
+ state: InitializedState,
+ subresourceRange: SubresourceRange
+ ): void {
+ assert(this.p.format in kTextureFormatInfo);
+ const format = this.p.format as EncodableTextureFormat;
+
+ const firstSubresource = subresourceRange.each().next().value;
+ assert(typeof firstSubresource !== 'undefined');
+
+ const [largestWidth, largestHeight, largestDepth] = virtualMipSize(
+ this.p.dimension,
+ [this.textureWidth, this.textureHeight, this.textureDepth],
+ firstSubresource.level
+ );
+
+ const rep = kTexelRepresentationInfo[format];
+ const texelData = new Uint8Array(rep.pack(rep.encode(this.stateToTexelComponents[state])));
+ const { buffer, bytesPerRow, rowsPerImage } = createTextureUploadBuffer(
+ texelData,
+ this.device,
+ format,
+ this.p.dimension,
+ [largestWidth, largestHeight, largestDepth]
+ );
+
+ const commandEncoder = this.device.createCommandEncoder();
+
+ for (const { level, layer } of subresourceRange.each()) {
+ const [width, height, depth] = virtualMipSize(
+ this.p.dimension,
+ [this.textureWidth, this.textureHeight, this.textureDepth],
+ level
+ );
+
+ commandEncoder.copyBufferToTexture(
+ {
+ buffer,
+ bytesPerRow,
+ rowsPerImage,
+ },
+ { texture, mipLevel: level, origin: { x: 0, y: 0, z: layer } },
+ { width, height, depthOrArrayLayers: depth }
+ );
+ }
+ this.queue.submit([commandEncoder.finish()]);
+ buffer.destroy();
+ }
+
+ initializeTexture(
+ texture: GPUTexture,
+ state: InitializedState,
+ subresourceRange: SubresourceRange
+ ): void {
+ const info = kTextureFormatInfo[this.p.format];
+ if (this.p.sampleCount > 1 || !allAspectsCopyDst(info)) {
+ // Copies to multisampled textures not yet specified.
+ // Use a storeOp for now.
+ if (info.color) assert(!!info.colorRender, 'not implemented for non-renderable color');
+ this.initializeWithStoreOp(state, texture, subresourceRange);
+ } else {
+ this.initializeWithCopy(texture, state, subresourceRange);
+ }
+ }
+
+ discardTexture(texture: GPUTexture, subresourceRange: SubresourceRange): void {
+ const commandEncoder = this.device.createCommandEncoder();
+ commandEncoder.pushDebugGroup('discardTexture');
+
+ for (const desc of this.generateTextureViewDescriptorsForRendering('all', subresourceRange)) {
+ if (kTextureFormatInfo[this.p.format].color) {
+ commandEncoder
+ .beginRenderPass({
+ colorAttachments: [
+ {
+ view: texture.createView(desc),
+ storeOp: 'discard',
+ loadOp: 'load',
+ },
+ ],
+ })
+ .end();
+ } else {
+ const depthStencilAttachment: GPURenderPassDepthStencilAttachment = {
+ view: texture.createView(desc),
+ };
+ if (kTextureFormatInfo[this.p.format].depth) {
+ depthStencilAttachment.depthLoadOp = 'load';
+ depthStencilAttachment.depthStoreOp = 'discard';
+ }
+ if (kTextureFormatInfo[this.p.format].stencil) {
+ depthStencilAttachment.stencilLoadOp = 'load';
+ depthStencilAttachment.stencilStoreOp = 'discard';
+ }
+ commandEncoder
+ .beginRenderPass({
+ colorAttachments: [],
+ depthStencilAttachment,
+ })
+ .end();
+ }
+ }
+
+ commandEncoder.popDebugGroup();
+ this.queue.submit([commandEncoder.finish()]);
+ }
+}
+
+export const kTestParams = kUnitCaseParamsBuilder
+ .combine('dimension', kTextureDimensions)
+ .combine('readMethod', [
+ ReadMethod.CopyToBuffer,
+ ReadMethod.CopyToTexture,
+ ReadMethod.Sample,
+ ReadMethod.DepthTest,
+ ReadMethod.StencilTest,
+ ])
+ // [3] compressed formats
+ .combine('format', kUncompressedTextureFormats)
+ .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format))
+ .beginSubcases()
+ .combine('aspect', kTextureAspects)
+ .unless(({ readMethod, format, aspect }) => {
+ const info = kTextureFormatInfo[format];
+ return (
+ (readMethod === ReadMethod.DepthTest && (!info.depth || aspect === 'stencil-only')) ||
+ (readMethod === ReadMethod.StencilTest && (!info.stencil || aspect === 'depth-only')) ||
+ (readMethod === ReadMethod.ColorBlending && !info.color) ||
+ // [1]: Test with depth/stencil sampling
+ (readMethod === ReadMethod.Sample && (!!info.depth || !!info.stencil)) ||
+ (aspect === 'depth-only' && !info.depth) ||
+ (aspect === 'stencil-only' && !info.stencil) ||
+ (aspect === 'all' && !!info.depth && !!info.stencil) ||
+ // Cannot copy from a packed depth format.
+ // [2]: Test copying out of the stencil aspect.
+ ((readMethod === ReadMethod.CopyToBuffer || readMethod === ReadMethod.CopyToTexture) &&
+ (format === 'depth24plus' || format === 'depth24plus-stencil8'))
+ );
+ })
+ .combine('mipLevelCount', kMipLevelCounts)
+ // 1D texture can only have a single mip level
+ .unless(p => p.dimension === '1d' && p.mipLevelCount !== 1)
+ .combine('sampleCount', kSampleCounts)
+ .unless(
+ ({ readMethod, sampleCount }) =>
+ // We can only read from multisampled textures by sampling.
+ sampleCount > 1 &&
+ (readMethod === ReadMethod.CopyToBuffer || readMethod === ReadMethod.CopyToTexture)
+ )
+ // Multisampled textures may only have one mip
+ .unless(({ sampleCount, mipLevelCount }) => sampleCount > 1 && mipLevelCount > 1)
+ .combine('uninitializeMethod', kUninitializeMethods)
+ .unless(({ dimension, readMethod, uninitializeMethod, format, sampleCount }) => {
+ const formatInfo = kTextureFormatInfo[format];
+ return (
+ dimension !== '2d' &&
+ (sampleCount > 1 ||
+ !!formatInfo.depth ||
+ !!formatInfo.stencil ||
+ readMethod === ReadMethod.DepthTest ||
+ readMethod === ReadMethod.StencilTest ||
+ readMethod === ReadMethod.ColorBlending ||
+ uninitializeMethod === UninitializeMethod.StoreOpClear)
+ );
+ })
+ .expandWithParams(function* ({ dimension }) {
+ switch (dimension) {
+ case '2d':
+ yield { layerCount: 1 as LayerCounts };
+ yield { layerCount: 7 as LayerCounts };
+ break;
+ case '1d':
+ case '3d':
+ yield { layerCount: 1 as LayerCounts };
+ break;
+ }
+ })
+ // Multisampled 3D / 2D array textures not supported.
+ .unless(({ sampleCount, layerCount }) => sampleCount > 1 && layerCount > 1)
+ .unless(({ format, sampleCount, uninitializeMethod, readMethod }) => {
+ const usage = getRequiredTextureUsage(format, sampleCount, uninitializeMethod, readMethod);
+ const info = kTextureFormatInfo[format];
+
+ return (
+ ((usage & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0 &&
+ info.color &&
+ !info.colorRender) ||
+ ((usage & GPUConst.TextureUsage.STORAGE_BINDING) !== 0 && !info.color?.storage) ||
+ (sampleCount > 1 && !info.multisample)
+ );
+ })
+ .combine('nonPowerOfTwo', [false, true])
+ .combine('canaryOnCreation', [false, true]);
+
+type TextureZeroParams = ParamTypeOf<typeof kTestParams>;
+
+export type CheckContents = (
+ t: TextureZeroInitTest,
+ params: TextureZeroParams,
+ texture: GPUTexture,
+ state: InitializedState,
+ subresourceRange: SubresourceRange
+) => void;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/texture_zero.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/texture_zero.spec.ts
index 3f0baeccbd..ee4c141aef 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/texture_zero.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/resource_init/texture_zero.spec.ts
@@ -7,550 +7,9 @@ TODO:
- test compressed texture formats [3]
`;
-// MAINTENANCE_TODO: This is a test file, it probably shouldn't export anything.
-// Everything that's exported should be moved to another file.
-
-import { TestCaseRecorder, TestParams } from '../../../../common/framework/fixture.js';
-import {
- kUnitCaseParamsBuilder,
- ParamTypeOf,
-} from '../../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../../common/framework/test_group.js';
-import { assert, unreachable } from '../../../../common/util/util.js';
-import { kTextureAspects, kTextureDimensions } from '../../../capability_info.js';
-import { GPUConst } from '../../../constants.js';
-import {
- kTextureFormatInfo,
- kUncompressedTextureFormats,
- textureDimensionAndFormatCompatible,
- UncompressedTextureFormat,
- EncodableTextureFormat,
-} from '../../../format_info.js';
-import { GPUTest, GPUTestSubcaseBatchState } from '../../../gpu_test.js';
-import { virtualMipSize } from '../../../util/texture/base.js';
-import { createTextureUploadBuffer } from '../../../util/texture/layout.js';
-import { BeginEndRange, SubresourceRange } from '../../../util/texture/subresource.js';
-import { PerTexelComponent, kTexelRepresentationInfo } from '../../../util/texture/texel_data.js';
-
-export enum UninitializeMethod {
- Creation = 'Creation', // The texture was just created. It is uninitialized.
- StoreOpClear = 'StoreOpClear', // The texture was rendered to with GPUStoreOp "clear"
-}
-const kUninitializeMethods = Object.keys(UninitializeMethod) as UninitializeMethod[];
-
-export const enum ReadMethod {
- Sample = 'Sample', // The texture is sampled from
- CopyToBuffer = 'CopyToBuffer', // The texture is copied to a buffer
- CopyToTexture = 'CopyToTexture', // The texture is copied to another texture
- DepthTest = 'DepthTest', // The texture is read as a depth buffer
- StencilTest = 'StencilTest', // The texture is read as a stencil buffer
- ColorBlending = 'ColorBlending', // Read the texture by blending as a color attachment
- Storage = 'Storage', // Read the texture as a storage texture
-}
-
-// Test with these mip level counts
-type MipLevels = 1 | 5;
-const kMipLevelCounts: MipLevels[] = [1, 5];
-
-// For each mip level count, define the mip ranges to leave uninitialized.
-const kUninitializedMipRangesToTest: { [k in MipLevels]: BeginEndRange[] } = {
- 1: [{ begin: 0, end: 1 }], // Test the only mip
- 5: [
- { begin: 0, end: 2 },
- { begin: 3, end: 4 },
- ], // Test a range and a single mip
-};
-
-// Test with these sample counts.
-const kSampleCounts: number[] = [1, 4];
-
-// Test with these layer counts.
-type LayerCounts = 1 | 7;
-
-// For each layer count, define the layers to leave uninitialized.
-const kUninitializedLayerRangesToTest: { [k in LayerCounts]: BeginEndRange[] } = {
- 1: [{ begin: 0, end: 1 }], // Test the only layer
- 7: [
- { begin: 2, end: 4 },
- { begin: 6, end: 7 },
- ], // Test a range and a single layer
-};
-
-// Enums to abstract over color / depth / stencil values in textures. Depending on the texture format,
-// the data for each value may have a different representation. These enums are converted to a
-// representation such that their values can be compared. ex.) An integer is needed to upload to an
-// unsigned normalized format, but its value is read as a float in the shader.
-export const enum InitializedState {
- Canary, // Set on initialized subresources. It should stay the same. On discarded resources, we should observe zero.
- Zero, // We check that uninitialized subresources are in this state when read back.
-}
-
-const initializedStateAsFloat = {
- [InitializedState.Zero]: 0,
- [InitializedState.Canary]: 1,
-};
-
-const initializedStateAsUint = {
- [InitializedState.Zero]: 0,
- [InitializedState.Canary]: 1,
-};
-
-const initializedStateAsSint = {
- [InitializedState.Zero]: 0,
- [InitializedState.Canary]: -1,
-};
-
-function initializedStateAsColor(
- state: InitializedState,
- format: GPUTextureFormat
-): [number, number, number, number] {
- let value;
- if (format.indexOf('uint') !== -1) {
- value = initializedStateAsUint[state];
- } else if (format.indexOf('sint') !== -1) {
- value = initializedStateAsSint[state];
- } else {
- value = initializedStateAsFloat[state];
- }
- return [value, value, value, value];
-}
-
-const initializedStateAsDepth = {
- [InitializedState.Zero]: 0,
- [InitializedState.Canary]: 0.8,
-};
-
-const initializedStateAsStencil = {
- [InitializedState.Zero]: 0,
- [InitializedState.Canary]: 42,
-};
-
-function getRequiredTextureUsage(
- format: UncompressedTextureFormat,
- sampleCount: number,
- uninitializeMethod: UninitializeMethod,
- readMethod: ReadMethod
-): GPUTextureUsageFlags {
- let usage: GPUTextureUsageFlags = GPUConst.TextureUsage.COPY_DST;
-
- switch (uninitializeMethod) {
- case UninitializeMethod.Creation:
- break;
- case UninitializeMethod.StoreOpClear:
- usage |= GPUConst.TextureUsage.RENDER_ATTACHMENT;
- break;
- default:
- unreachable();
- }
-
- switch (readMethod) {
- case ReadMethod.CopyToBuffer:
- case ReadMethod.CopyToTexture:
- usage |= GPUConst.TextureUsage.COPY_SRC;
- break;
- case ReadMethod.Sample:
- usage |= GPUConst.TextureUsage.TEXTURE_BINDING;
- break;
- case ReadMethod.Storage:
- usage |= GPUConst.TextureUsage.STORAGE_BINDING;
- break;
- case ReadMethod.DepthTest:
- case ReadMethod.StencilTest:
- case ReadMethod.ColorBlending:
- usage |= GPUConst.TextureUsage.RENDER_ATTACHMENT;
- break;
- default:
- unreachable();
- }
-
- if (sampleCount > 1) {
- // Copies to multisampled textures are not allowed. We need OutputAttachment to initialize
- // canary data in multisampled textures.
- usage |= GPUConst.TextureUsage.RENDER_ATTACHMENT;
- }
-
- if (!kTextureFormatInfo[format].copyDst) {
- // Copies are not possible. We need OutputAttachment to initialize
- // canary data.
- assert(kTextureFormatInfo[format].renderable);
- usage |= GPUConst.TextureUsage.RENDER_ATTACHMENT;
- }
-
- return usage;
-}
-
-export class TextureZeroInitTest extends GPUTest {
- readonly stateToTexelComponents: { [k in InitializedState]: PerTexelComponent<number> };
-
- private p: TextureZeroParams;
- constructor(sharedState: GPUTestSubcaseBatchState, rec: TestCaseRecorder, params: TestParams) {
- super(sharedState, rec, params);
- this.p = params as TextureZeroParams;
-
- const stateToTexelComponents = (state: InitializedState) => {
- const [R, G, B, A] = initializedStateAsColor(state, this.p.format);
- return {
- R,
- G,
- B,
- A,
- Depth: initializedStateAsDepth[state],
- Stencil: initializedStateAsStencil[state],
- };
- };
-
- this.stateToTexelComponents = {
- [InitializedState.Zero]: stateToTexelComponents(InitializedState.Zero),
- [InitializedState.Canary]: stateToTexelComponents(InitializedState.Canary),
- };
- }
-
- get textureWidth(): number {
- let width = 1 << this.p.mipLevelCount;
- if (this.p.nonPowerOfTwo) {
- width = 2 * width - 1;
- }
- return width;
- }
-
- get textureHeight(): number {
- if (this.p.dimension === '1d') {
- return 1;
- }
-
- let height = 1 << this.p.mipLevelCount;
- if (this.p.nonPowerOfTwo) {
- height = 2 * height - 1;
- }
- return height;
- }
-
- get textureDepth(): number {
- return this.p.dimension === '3d' ? 11 : 1;
- }
-
- get textureDepthOrArrayLayers(): number {
- return this.p.dimension === '2d' ? this.p.layerCount : this.textureDepth;
- }
-
- // Used to iterate subresources and check that their uninitialized contents are zero when accessed
- *iterateUninitializedSubresources(): Generator<SubresourceRange> {
- for (const mipRange of kUninitializedMipRangesToTest[this.p.mipLevelCount]) {
- for (const layerRange of kUninitializedLayerRangesToTest[this.p.layerCount]) {
- yield new SubresourceRange({ mipRange, layerRange });
- }
- }
- }
-
- // Used to iterate and initialize other subresources not checked for zero-initialization.
- // Zero-initialization of uninitialized subresources should not have side effects on already
- // initialized subresources.
- *iterateInitializedSubresources(): Generator<SubresourceRange> {
- const uninitialized: boolean[][] = new Array(this.p.mipLevelCount);
- for (let level = 0; level < uninitialized.length; ++level) {
- uninitialized[level] = new Array(this.p.layerCount);
- }
- for (const subresources of this.iterateUninitializedSubresources()) {
- for (const { level, layer } of subresources.each()) {
- uninitialized[level][layer] = true;
- }
- }
- for (let level = 0; level < uninitialized.length; ++level) {
- for (let layer = 0; layer < uninitialized[level].length; ++layer) {
- if (!uninitialized[level][layer]) {
- yield new SubresourceRange({
- mipRange: { begin: level, count: 1 },
- layerRange: { begin: layer, count: 1 },
- });
- }
- }
- }
- }
-
- *generateTextureViewDescriptorsForRendering(
- aspect: GPUTextureAspect,
- subresourceRange?: SubresourceRange
- ): Generator<GPUTextureViewDescriptor> {
- const viewDescriptor: GPUTextureViewDescriptor = {
- dimension: '2d',
- aspect,
- };
-
- if (subresourceRange === undefined) {
- return viewDescriptor;
- }
-
- for (const { level, layer } of subresourceRange.each()) {
- yield {
- ...viewDescriptor,
- baseMipLevel: level,
- mipLevelCount: 1,
- baseArrayLayer: layer,
- arrayLayerCount: 1,
- };
- }
- }
-
- private initializeWithStoreOp(
- state: InitializedState,
- texture: GPUTexture,
- subresourceRange?: SubresourceRange
- ): void {
- const commandEncoder = this.device.createCommandEncoder();
- commandEncoder.pushDebugGroup('initializeWithStoreOp');
-
- for (const viewDescriptor of this.generateTextureViewDescriptorsForRendering(
- 'all',
- subresourceRange
- )) {
- if (kTextureFormatInfo[this.p.format].color) {
- commandEncoder
- .beginRenderPass({
- colorAttachments: [
- {
- view: texture.createView(viewDescriptor),
- storeOp: 'store',
- clearValue: initializedStateAsColor(state, this.p.format),
- loadOp: 'clear',
- },
- ],
- })
- .end();
- } else {
- const depthStencilAttachment: GPURenderPassDepthStencilAttachment = {
- view: texture.createView(viewDescriptor),
- };
- if (kTextureFormatInfo[this.p.format].depth) {
- depthStencilAttachment.depthClearValue = initializedStateAsDepth[state];
- depthStencilAttachment.depthLoadOp = 'clear';
- depthStencilAttachment.depthStoreOp = 'store';
- }
- if (kTextureFormatInfo[this.p.format].stencil) {
- depthStencilAttachment.stencilClearValue = initializedStateAsStencil[state];
- depthStencilAttachment.stencilLoadOp = 'clear';
- depthStencilAttachment.stencilStoreOp = 'store';
- }
- commandEncoder
- .beginRenderPass({
- colorAttachments: [],
- depthStencilAttachment,
- })
- .end();
- }
- }
-
- commandEncoder.popDebugGroup();
- this.queue.submit([commandEncoder.finish()]);
- }
-
- private initializeWithCopy(
- texture: GPUTexture,
- state: InitializedState,
- subresourceRange: SubresourceRange
- ): void {
- assert(this.p.format in kTextureFormatInfo);
- const format = this.p.format as EncodableTextureFormat;
-
- const firstSubresource = subresourceRange.each().next().value;
- assert(typeof firstSubresource !== 'undefined');
-
- const [largestWidth, largestHeight, largestDepth] = virtualMipSize(
- this.p.dimension,
- [this.textureWidth, this.textureHeight, this.textureDepth],
- firstSubresource.level
- );
-
- const rep = kTexelRepresentationInfo[format];
- const texelData = new Uint8Array(rep.pack(rep.encode(this.stateToTexelComponents[state])));
- const { buffer, bytesPerRow, rowsPerImage } = createTextureUploadBuffer(
- texelData,
- this.device,
- format,
- this.p.dimension,
- [largestWidth, largestHeight, largestDepth]
- );
-
- const commandEncoder = this.device.createCommandEncoder();
-
- for (const { level, layer } of subresourceRange.each()) {
- const [width, height, depth] = virtualMipSize(
- this.p.dimension,
- [this.textureWidth, this.textureHeight, this.textureDepth],
- level
- );
-
- commandEncoder.copyBufferToTexture(
- {
- buffer,
- bytesPerRow,
- rowsPerImage,
- },
- { texture, mipLevel: level, origin: { x: 0, y: 0, z: layer } },
- { width, height, depthOrArrayLayers: depth }
- );
- }
- this.queue.submit([commandEncoder.finish()]);
- buffer.destroy();
- }
-
- initializeTexture(
- texture: GPUTexture,
- state: InitializedState,
- subresourceRange: SubresourceRange
- ): void {
- if (this.p.sampleCount > 1 || !kTextureFormatInfo[this.p.format].copyDst) {
- // Copies to multisampled textures not yet specified.
- // Use a storeOp for now.
- assert(kTextureFormatInfo[this.p.format].renderable);
- this.initializeWithStoreOp(state, texture, subresourceRange);
- } else {
- this.initializeWithCopy(texture, state, subresourceRange);
- }
- }
-
- discardTexture(texture: GPUTexture, subresourceRange: SubresourceRange): void {
- const commandEncoder = this.device.createCommandEncoder();
- commandEncoder.pushDebugGroup('discardTexture');
-
- for (const desc of this.generateTextureViewDescriptorsForRendering('all', subresourceRange)) {
- if (kTextureFormatInfo[this.p.format].color) {
- commandEncoder
- .beginRenderPass({
- colorAttachments: [
- {
- view: texture.createView(desc),
- storeOp: 'discard',
- loadOp: 'load',
- },
- ],
- })
- .end();
- } else {
- const depthStencilAttachment: GPURenderPassDepthStencilAttachment = {
- view: texture.createView(desc),
- };
- if (kTextureFormatInfo[this.p.format].depth) {
- depthStencilAttachment.depthLoadOp = 'load';
- depthStencilAttachment.depthStoreOp = 'discard';
- }
- if (kTextureFormatInfo[this.p.format].stencil) {
- depthStencilAttachment.stencilLoadOp = 'load';
- depthStencilAttachment.stencilStoreOp = 'discard';
- }
- commandEncoder
- .beginRenderPass({
- colorAttachments: [],
- depthStencilAttachment,
- })
- .end();
- }
- }
-
- commandEncoder.popDebugGroup();
- this.queue.submit([commandEncoder.finish()]);
- }
-}
-
-const kTestParams = kUnitCaseParamsBuilder
- .combine('dimension', kTextureDimensions)
- .combine('readMethod', [
- ReadMethod.CopyToBuffer,
- ReadMethod.CopyToTexture,
- ReadMethod.Sample,
- ReadMethod.DepthTest,
- ReadMethod.StencilTest,
- ])
- // [3] compressed formats
- .combine('format', kUncompressedTextureFormats)
- .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format))
- .beginSubcases()
- .combine('aspect', kTextureAspects)
- .unless(({ readMethod, format, aspect }) => {
- const info = kTextureFormatInfo[format];
- return (
- (readMethod === ReadMethod.DepthTest && (!info.depth || aspect === 'stencil-only')) ||
- (readMethod === ReadMethod.StencilTest && (!info.stencil || aspect === 'depth-only')) ||
- (readMethod === ReadMethod.ColorBlending && !info.color) ||
- // [1]: Test with depth/stencil sampling
- (readMethod === ReadMethod.Sample && (!!info.depth || !!info.stencil)) ||
- (aspect === 'depth-only' && !info.depth) ||
- (aspect === 'stencil-only' && !info.stencil) ||
- (aspect === 'all' && !!info.depth && !!info.stencil) ||
- // Cannot copy from a packed depth format.
- // [2]: Test copying out of the stencil aspect.
- ((readMethod === ReadMethod.CopyToBuffer || readMethod === ReadMethod.CopyToTexture) &&
- (format === 'depth24plus' || format === 'depth24plus-stencil8'))
- );
- })
- .combine('mipLevelCount', kMipLevelCounts)
- // 1D texture can only have a single mip level
- .unless(p => p.dimension === '1d' && p.mipLevelCount !== 1)
- .combine('sampleCount', kSampleCounts)
- .unless(
- ({ readMethod, sampleCount }) =>
- // We can only read from multisampled textures by sampling.
- sampleCount > 1 &&
- (readMethod === ReadMethod.CopyToBuffer || readMethod === ReadMethod.CopyToTexture)
- )
- // Multisampled textures may only have one mip
- .unless(({ sampleCount, mipLevelCount }) => sampleCount > 1 && mipLevelCount > 1)
- .combine('uninitializeMethod', kUninitializeMethods)
- .unless(({ dimension, readMethod, uninitializeMethod, format, sampleCount }) => {
- const formatInfo = kTextureFormatInfo[format];
- return (
- dimension !== '2d' &&
- (sampleCount > 1 ||
- !!formatInfo.depth ||
- !!formatInfo.stencil ||
- readMethod === ReadMethod.DepthTest ||
- readMethod === ReadMethod.StencilTest ||
- readMethod === ReadMethod.ColorBlending ||
- uninitializeMethod === UninitializeMethod.StoreOpClear)
- );
- })
- .expandWithParams(function* ({ dimension }) {
- switch (dimension) {
- case '2d':
- yield { layerCount: 1 as LayerCounts };
- yield { layerCount: 7 as LayerCounts };
- break;
- case '1d':
- case '3d':
- yield { layerCount: 1 as LayerCounts };
- break;
- }
- })
- // Multisampled 3D / 2D array textures not supported.
- .unless(({ sampleCount, layerCount }) => sampleCount > 1 && layerCount > 1)
- .unless(({ format, sampleCount, uninitializeMethod, readMethod }) => {
- const usage = getRequiredTextureUsage(format, sampleCount, uninitializeMethod, readMethod);
- const info = kTextureFormatInfo[format];
-
- return (
- ((usage & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0 && !info.renderable) ||
- ((usage & GPUConst.TextureUsage.STORAGE_BINDING) !== 0 && !info.color?.storage) ||
- (sampleCount > 1 && !info.multisample)
- );
- })
- .combine('nonPowerOfTwo', [false, true])
- .combine('canaryOnCreation', [false, true])
- .filter(({ canaryOnCreation, format }) => {
- // We can only initialize the texture if it's encodable or renderable.
- const canInitialize = format in kTextureFormatInfo || kTextureFormatInfo[format].renderable;
-
- // Filter out cases where we want canary values but can't initialize.
- return !canaryOnCreation || canInitialize;
- });
-
-type TextureZeroParams = ParamTypeOf<typeof kTestParams>;
-
-export type CheckContents = (
- t: TextureZeroInitTest,
- params: TextureZeroParams,
- texture: GPUTexture,
- state: InitializedState,
- subresourceRange: SubresourceRange
-) => void;
+import { unreachable } from '../../../../common/util/util.js';
+import { kTextureFormatInfo } from '../../../format_info.js';
import { checkContentsByBufferCopy, checkContentsByTextureCopy } from './check_texture/by_copy.js';
import {
@@ -558,6 +17,15 @@ import {
checkContentsByStencilTest,
} from './check_texture/by_ds_test.js';
import { checkContentsBySampling } from './check_texture/by_sampling.js';
+import {
+ getRequiredTextureUsage,
+ ReadMethod,
+ CheckContents,
+ TextureZeroInitTest,
+ kTestParams,
+ UninitializeMethod,
+ InitializedState,
+} from './check_texture/texture_zero_init_test.js';
const checkContentsImpl: { [k in ReadMethod]: CheckContents } = {
Sample: checkContentsBySampling,
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/shader_module/compilation_info.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/shader_module/compilation_info.spec.ts
index 93fa4575c4..3382dabc37 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/shader_module/compilation_info.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/shader_module/compilation_info.spec.ts
@@ -64,6 +64,17 @@ const kInvalidShaderSources = [
return unknown(0.0, 0.0, 0.0, 1.0);
}`,
},
+ {
+ valid: false,
+ name: 'unicode-multi-byte-characters',
+ _errorLine: 1,
+ // This shader is simplistic enough to always result in the same error position.
+ // Generally, various backends may choose to report the error at different positions within the
+ // line, so it's difficult to meaningfully validate them.
+ _errorLinePos: 19,
+ _code: `/*🐈🐈🐈🐈🐈🐈🐈*/?
+// Expected Error: invalid character found`,
+ },
];
const kAllShaderSources = [...kValidShaderSources, ...kInvalidShaderSources];
@@ -174,7 +185,7 @@ g.test('line_number_and_position')
.combine('sourceMapName', kSourceMapsKeys)
)
.fn(async t => {
- const { _code, _errorLine, sourceMapName } = t.params;
+ const { _code, _errorLine, _errorLinePos, sourceMapName } = t.params;
const shaderModule = t.expectGPUError('validation', () => {
const sourceMap = kSourceMaps[sourceMapName];
@@ -191,14 +202,22 @@ g.test('line_number_and_position')
// If a line is reported, it should point at the correct line (1-based).
t.expect(
(message.lineNum === 0) === (message.linePos === 0),
- "GPUCompilationMessages that don't report a line number should not report a line position."
+ `Got message.lineNum ${message.lineNum}, .linePos ${message.linePos}, but GPUCompilationMessage should specify both or neither`
);
- if (message.lineNum === 0 || message.lineNum === _errorLine) {
+ if (message.lineNum === 0) {
foundAppropriateError = true;
+ break;
+ }
- // Various backends may choose to report the error at different positions within the line,
- // so it's difficult to meaningfully validate them.
+ if (message.lineNum === _errorLine) {
+ foundAppropriateError = true;
+ if (_errorLinePos !== undefined) {
+ t.expect(
+ message.linePos === _errorLinePos,
+ `Got message.linePos ${message.linePos}, expected ${_errorLinePos}`
+ );
+ }
break;
}
}
@@ -239,10 +258,9 @@ g.test('offset_and_length')
for (const message of info.messages) {
// Any offsets and lengths should reference valid spans of the shader code.
- t.expect(message.offset <= _code.length, 'Message offset should be within the shader source');
t.expect(
- message.offset + message.length <= _code.length,
- 'Message offset and length should be within the shader source'
+ message.offset <= _code.length && message.offset + message.length <= _code.length,
+ 'message.offset and .length should be within the shader source'
);
// If a valid line number and position are given, the offset should point the the same
@@ -255,9 +273,10 @@ g.test('offset_and_length')
lineOffset += 1;
}
+ const expectedOffset = lineOffset + message.linePos - 1;
t.expect(
- message.offset === lineOffset + message.linePos - 1,
- 'lineNum and linePos should point to the same location as offset'
+ message.offset === expectedOffset,
+ `message.lineNum (${message.lineNum}) and .linePos (${message.linePos}) point to a different offset (${lineOffset} + ${message.linePos} - 1 = ${expectedOffset}) than .offset (${message.offset})`
);
}
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/storage_texture/read_only.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/storage_texture/read_only.spec.ts
new file mode 100644
index 0000000000..978924aabd
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/storage_texture/read_only.spec.ts
@@ -0,0 +1,626 @@
+export const description = `
+Tests for the behavior of read-only storage textures.
+
+TODO:
+- Test mipmap level > 0
+- Test resource usage transitions with read-only storage textures
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { unreachable, assert } from '../../../../common/util/util.js';
+import { Float16Array } from '../../../../external/petamoriken/float16/float16.js';
+import { kTextureDimensions } from '../../../capability_info.js';
+import {
+ ColorTextureFormat,
+ kColorTextureFormats,
+ kTextureFormatInfo,
+} from '../../../format_info.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { kValidShaderStages, TValidShaderStage } from '../../../util/shader.js';
+
+function ComponentCount(format: ColorTextureFormat): number {
+ switch (format) {
+ case 'r32float':
+ case 'r32sint':
+ case 'r32uint':
+ return 1;
+ case 'rg32float':
+ case 'rg32sint':
+ case 'rg32uint':
+ return 2;
+ case 'rgba32float':
+ case 'rgba32sint':
+ case 'rgba32uint':
+ case 'rgba8sint':
+ case 'rgba8uint':
+ case 'rgba8snorm':
+ case 'rgba8unorm':
+ case 'rgba16float':
+ case 'rgba16sint':
+ case 'rgba16uint':
+ case 'bgra8unorm':
+ return 4;
+ default:
+ unreachable();
+ return 0;
+ }
+}
+
+class F extends GPUTest {
+ InitTextureAndGetExpectedOutputBufferData(
+ storageTexture: GPUTexture,
+ format: ColorTextureFormat
+ ): ArrayBuffer {
+ const bytesPerBlock = kTextureFormatInfo[format].color.bytes;
+ assert(bytesPerBlock !== undefined);
+
+ const width = storageTexture.width;
+ const height = storageTexture.height;
+ const depthOrArrayLayers = storageTexture.depthOrArrayLayers;
+
+ const texelData = new ArrayBuffer(bytesPerBlock * width * height * depthOrArrayLayers);
+ const texelTypedDataView = this.GetTypedArrayBufferViewForTexelData(texelData, format);
+ const componentCount = ComponentCount(format);
+ const outputBufferData = new ArrayBuffer(4 * 4 * width * height * depthOrArrayLayers);
+ const outputBufferTypedData = this.GetTypedArrayBufferForOutputBufferData(
+ outputBufferData,
+ format
+ );
+
+ const SetData = (
+ texelValue: number,
+ outputValue: number,
+ texelDataIndex: number,
+ component: number,
+ outputComponent: number = component
+ ) => {
+ const texelComponentIndex = texelDataIndex * componentCount + component;
+ texelTypedDataView[texelComponentIndex] = texelValue;
+ const outputTexelComponentIndex = texelDataIndex * 4 + outputComponent;
+ outputBufferTypedData[outputTexelComponentIndex] = outputValue;
+ };
+ for (let z = 0; z < depthOrArrayLayers; ++z) {
+ for (let y = 0; y < height; ++y) {
+ for (let x = 0; x < width; ++x) {
+ const texelDataIndex = z * width * height + y * width + x;
+ outputBufferTypedData[4 * texelDataIndex] = 0;
+ outputBufferTypedData[4 * texelDataIndex + 1] = 0;
+ outputBufferTypedData[4 * texelDataIndex + 2] = 0;
+ outputBufferTypedData[4 * texelDataIndex + 3] = 1;
+ for (let component = 0; component < componentCount; ++component) {
+ switch (format) {
+ case 'r32uint':
+ case 'rg32uint':
+ case 'rgba16uint':
+ case 'rgba32uint': {
+ const texelValue = 4 * texelDataIndex + component + 1;
+ SetData(texelValue, texelValue, texelDataIndex, component);
+ break;
+ }
+ case 'rgba8uint': {
+ const texelValue = (4 * texelDataIndex + component + 1) % 256;
+ SetData(texelValue, texelValue, texelDataIndex, component);
+ break;
+ }
+ case 'rgba8unorm': {
+ const texelValue = (4 * texelDataIndex + component + 1) % 256;
+ const outputValue = texelValue / 255.0;
+ SetData(texelValue, outputValue, texelDataIndex, component);
+ break;
+ }
+ case 'bgra8unorm': {
+ const texelValue = (4 * texelDataIndex + component + 1) % 256;
+ const outputValue = texelValue / 255.0;
+ // BGRA -> RGBA
+ assert(component < 4);
+ const outputComponent = [2, 1, 0, 3][component];
+ SetData(texelValue, outputValue, texelDataIndex, component, outputComponent);
+ break;
+ }
+ case 'r32sint':
+ case 'rg32sint':
+ case 'rgba16sint':
+ case 'rgba32sint': {
+ const texelValue =
+ (texelDataIndex & 1 ? 1 : -1) * (4 * texelDataIndex + component + 1);
+ SetData(texelValue, texelValue, texelDataIndex, component);
+ break;
+ }
+ case 'rgba8sint': {
+ const texelValue = ((4 * texelDataIndex + component + 1) % 256) - 128;
+ SetData(texelValue, texelValue, texelDataIndex, component);
+ break;
+ }
+ case 'rgba8snorm': {
+ const texelValue = ((4 * texelDataIndex + component + 1) % 256) - 128;
+ const outputValue = Math.max(texelValue / 127.0, -1.0);
+ SetData(texelValue, outputValue, texelDataIndex, component);
+ break;
+ }
+ case 'r32float':
+ case 'rg32float':
+ case 'rgba32float': {
+ const texelValue = (4 * texelDataIndex + component + 1) / 10.0;
+ SetData(texelValue, texelValue, texelDataIndex, component);
+ break;
+ }
+ case 'rgba16float': {
+ const texelValue = (4 * texelDataIndex + component + 1) / 10.0;
+ const f16Array = new Float16Array(1);
+ f16Array[0] = texelValue;
+ SetData(texelValue, f16Array[0], texelDataIndex, component);
+ break;
+ }
+ default:
+ unreachable();
+ break;
+ }
+ }
+ }
+ }
+ }
+ this.queue.writeTexture(
+ {
+ texture: storageTexture,
+ },
+ texelData,
+ {
+ bytesPerRow: bytesPerBlock * width,
+ rowsPerImage: height,
+ },
+ [width, height, depthOrArrayLayers]
+ );
+
+ return outputBufferData;
+ }
+
+ GetTypedArrayBufferForOutputBufferData(arrayBuffer: ArrayBuffer, format: ColorTextureFormat) {
+ switch (kTextureFormatInfo[format].color.type) {
+ case 'uint':
+ return new Uint32Array(arrayBuffer);
+ case 'sint':
+ return new Int32Array(arrayBuffer);
+ case 'float':
+ case 'unfilterable-float':
+ return new Float32Array(arrayBuffer);
+ }
+ }
+
+ GetTypedArrayBufferViewForTexelData(arrayBuffer: ArrayBuffer, format: ColorTextureFormat) {
+ switch (format) {
+ case 'r32uint':
+ case 'rg32uint':
+ case 'rgba32uint':
+ return new Uint32Array(arrayBuffer);
+ case 'rgba8uint':
+ case 'rgba8unorm':
+ case 'bgra8unorm':
+ return new Uint8Array(arrayBuffer);
+ case 'rgba16uint':
+ return new Uint16Array(arrayBuffer);
+ case 'r32sint':
+ case 'rg32sint':
+ case 'rgba32sint':
+ return new Int32Array(arrayBuffer);
+ case 'rgba8sint':
+ case 'rgba8snorm':
+ return new Int8Array(arrayBuffer);
+ case 'rgba16sint':
+ return new Int16Array(arrayBuffer);
+ case 'r32float':
+ case 'rg32float':
+ case 'rgba32float':
+ return new Float32Array(arrayBuffer);
+ case 'rgba16float':
+ return new Float16Array(arrayBuffer);
+ default:
+ unreachable();
+ return new Uint8Array(arrayBuffer);
+ }
+ }
+
+ GetOutputBufferWGSLType(format: ColorTextureFormat) {
+ switch (kTextureFormatInfo[format].color.type) {
+ case 'uint':
+ return 'vec4u';
+ case 'sint':
+ return 'vec4i';
+ case 'float':
+ case 'unfilterable-float':
+ return 'vec4f';
+ default:
+ unreachable();
+ return '';
+ }
+ }
+
+ DoTransform(
+ storageTexture: GPUTexture,
+ shaderStage: TValidShaderStage,
+ format: ColorTextureFormat,
+ outputBuffer: GPUBuffer
+ ) {
+ let declaration = '';
+ switch (storageTexture.dimension) {
+ case '1d':
+ declaration = 'texture_storage_1d';
+ break;
+ case '2d':
+ declaration =
+ storageTexture.depthOrArrayLayers > 1 ? 'texture_storage_2d_array' : 'texture_storage_2d';
+ break;
+ case '3d':
+ declaration = 'texture_storage_3d';
+ break;
+ }
+ const textureDeclaration = `
+ @group(0) @binding(0) var readOnlyTexture: ${declaration}<${format}, read>;
+ `;
+ const bindingResourceDeclaration = `
+ ${textureDeclaration}
+ @group(0) @binding(1)
+ var<storage,read_write> outputBuffer : array<${this.GetOutputBufferWGSLType(format)}>;
+ `;
+
+ const bindGroupEntries = [
+ {
+ binding: 0,
+ resource: storageTexture.createView(),
+ },
+ {
+ binding: 1,
+ resource: {
+ buffer: outputBuffer,
+ },
+ },
+ ];
+
+ const commandEncoder = this.device.createCommandEncoder();
+
+ switch (shaderStage) {
+ case 'compute': {
+ let textureLoadCoord = '';
+ switch (storageTexture.dimension) {
+ case '1d':
+ textureLoadCoord = 'invocationID.x';
+ break;
+ case '2d':
+ textureLoadCoord =
+ storageTexture.depthOrArrayLayers > 1
+ ? `vec2u(invocationID.x, invocationID.y), invocationID.z`
+ : `vec2u(invocationID.x, invocationID.y)`;
+ break;
+ case '3d':
+ textureLoadCoord = 'invocationID';
+ break;
+ }
+
+ const computeShader = `
+ ${bindingResourceDeclaration}
+ @compute
+ @workgroup_size(
+ ${storageTexture.width}, ${storageTexture.height}, ${storageTexture.depthOrArrayLayers})
+ fn main(
+ @builtin(local_invocation_id) invocationID: vec3u,
+ @builtin(local_invocation_index) invocationIndex: u32) {
+ let initialValue = textureLoad(readOnlyTexture, ${textureLoadCoord});
+ outputBuffer[invocationIndex] = initialValue;
+ }`;
+ const computePipeline = this.device.createComputePipeline({
+ compute: {
+ module: this.device.createShaderModule({
+ code: computeShader,
+ }),
+ },
+ layout: 'auto',
+ });
+ const bindGroup = this.device.createBindGroup({
+ layout: computePipeline.getBindGroupLayout(0),
+ entries: bindGroupEntries,
+ });
+
+ const computePassEncoder = commandEncoder.beginComputePass();
+ computePassEncoder.setPipeline(computePipeline);
+ computePassEncoder.setBindGroup(0, bindGroup);
+ computePassEncoder.dispatchWorkgroups(1);
+ computePassEncoder.end();
+ break;
+ }
+ case 'fragment': {
+ let textureLoadCoord = '';
+ switch (storageTexture.dimension) {
+ case '1d':
+ textureLoadCoord = 'textureCoord.x';
+ break;
+ case '2d':
+ textureLoadCoord =
+ storageTexture.depthOrArrayLayers > 1 ? 'textureCoord, z' : 'textureCoord';
+ break;
+ case '3d':
+ textureLoadCoord = 'vec3u(textureCoord, z)';
+ break;
+ }
+
+ const fragmentShader = `
+ ${bindingResourceDeclaration}
+ @fragment
+ fn main(@builtin(position) fragCoord: vec4f) -> @location(0) vec4f {
+ let textureCoord = vec2u(fragCoord.xy);
+ let storageTextureTexelCountPerImage = ${storageTexture.width * storageTexture.height}u;
+ for (var z = 0u; z < ${storageTexture.depthOrArrayLayers}; z++) {
+ let initialValue = textureLoad(readOnlyTexture, ${textureLoadCoord});
+ let outputIndex =
+ storageTextureTexelCountPerImage * z + textureCoord.y * ${storageTexture.width} +
+ textureCoord.x;
+ outputBuffer[outputIndex] = initialValue;
+ }
+ return vec4f(0.0, 1.0, 0.0, 1.0);
+ }`;
+ const vertexShader = `
+ @vertex
+ fn main(@builtin(vertex_index) vertexIndex : u32) -> @builtin(position) vec4f {
+ var 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));
+ return vec4f(pos[vertexIndex], 0.0, 1.0);
+ }
+ `;
+ const renderPipeline = this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: vertexShader,
+ }),
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: fragmentShader,
+ }),
+ targets: [
+ {
+ format: 'rgba8unorm',
+ },
+ ],
+ },
+ primitive: {
+ topology: 'triangle-list',
+ },
+ });
+
+ const bindGroup = this.device.createBindGroup({
+ layout: renderPipeline.getBindGroupLayout(0),
+ entries: bindGroupEntries,
+ });
+
+ const placeholderColorTexture = this.device.createTexture({
+ size: [storageTexture.width, storageTexture.height, 1],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ format: 'rgba8unorm',
+ });
+ this.trackForCleanup(placeholderColorTexture);
+
+ const renderPassEncoder = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: placeholderColorTexture.createView(),
+ loadOp: 'clear',
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
+ storeOp: 'store',
+ },
+ ],
+ });
+ renderPassEncoder.setPipeline(renderPipeline);
+ renderPassEncoder.setBindGroup(0, bindGroup);
+ renderPassEncoder.draw(6);
+ renderPassEncoder.end();
+ break;
+ }
+ case 'vertex': {
+ // For each texel location (coordX, coordY), draw one point at (coordX + 0.5, coordY + 0.5)
+ // in the storageTexture.width * storageTexture.height grid, and save all the texel values
+ // at (coordX, coordY, z) (z >= 0 && z < storageTexture.depthOrArrayLayers) into the
+ // corresponding vertex shader outputs.
+ let vertexOutputs = '';
+ for (let layer = 0; layer < storageTexture.depthOrArrayLayers; ++layer) {
+ vertexOutputs = vertexOutputs.concat(
+ `
+ @location(${layer + 1}) @interpolate(flat)
+ vertex_out${layer}: ${this.GetOutputBufferWGSLType(format)},`
+ );
+ }
+
+ let loadFromTextureWGSL = '';
+ switch (storageTexture.dimension) {
+ case '1d':
+ loadFromTextureWGSL = `
+ output.vertex_out0 = textureLoad(readOnlyTexture, coordX);`;
+ break;
+ case '2d':
+ if (storageTexture.depthOrArrayLayers === 1) {
+ loadFromTextureWGSL = `
+ output.vertex_out0 = textureLoad(readOnlyTexture, vec2u(coordX, coordY));`;
+ } else {
+ for (let z = 0; z < storageTexture.depthOrArrayLayers; ++z) {
+ loadFromTextureWGSL = loadFromTextureWGSL.concat(`
+ output.vertex_out${z} =
+ textureLoad(readOnlyTexture, vec2u(coordX, coordY), ${z});`);
+ }
+ }
+ break;
+ case '3d':
+ for (let z = 0; z < storageTexture.depthOrArrayLayers; ++z) {
+ loadFromTextureWGSL = loadFromTextureWGSL.concat(`
+ output.vertex_out${z} = textureLoad(readOnlyTexture, vec3u(coordX, coordY, ${z}));`);
+ }
+ break;
+ }
+
+ let outputToBufferWGSL = '';
+ for (let layer = 0; layer < storageTexture.depthOrArrayLayers; ++layer) {
+ outputToBufferWGSL = outputToBufferWGSL.concat(
+ `
+ let outputIndex${layer} =
+ storageTextureTexelCountPerImage * ${layer}u +
+ fragmentInput.tex_coord.y * ${storageTexture.width}u + fragmentInput.tex_coord.x;
+ outputBuffer[outputIndex${layer}] = fragmentInput.vertex_out${layer};`
+ );
+ }
+
+ const shader = `
+ ${bindingResourceDeclaration}
+ struct VertexOutput {
+ @builtin(position) my_pos: vec4f,
+ @location(0) @interpolate(flat) tex_coord: vec2u,
+ ${vertexOutputs}
+ }
+ @vertex
+ fn vs_main(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput {
+ var output : VertexOutput;
+ let coordX = vertexIndex % ${storageTexture.width}u;
+ let coordY = vertexIndex / ${storageTexture.width}u;
+ // Each vertex in the mesh take an even step along X axis from -1.0 to 1.0.
+ let posXStep = f32(${2.0 / storageTexture.width});
+ // As well as along Y axis.
+ let posYStep = f32(${2.0 / storageTexture.height});
+ // And the vertex located in the middle of the step, i.e. with a bias of 0.5 step.
+ let outputPosX = -1.0 + posXStep * 0.5 + posXStep * f32(coordX);
+ let outputPosY = -1.0 + posYStep * 0.5 + posYStep * f32(coordY);
+ output.my_pos = vec4f(outputPosX, outputPosY, 0.0, 1.0);
+ output.tex_coord = vec2u(coordX, coordY);
+ ${loadFromTextureWGSL}
+ return output;
+ }
+ @fragment
+ fn fs_main(fragmentInput : VertexOutput) -> @location(0) vec4f {
+ let storageTextureTexelCountPerImage = ${storageTexture.width * storageTexture.height}u;
+ ${outputToBufferWGSL}
+ return vec4f(0.0, 1.0, 0.0, 1.0);
+ }
+ `;
+
+ const renderPipeline = this.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: this.device.createShaderModule({
+ code: shader,
+ }),
+ },
+ fragment: {
+ module: this.device.createShaderModule({
+ code: shader,
+ }),
+ targets: [
+ {
+ format: 'rgba8unorm',
+ },
+ ],
+ },
+ primitive: {
+ topology: 'point-list',
+ },
+ });
+
+ const bindGroup = this.device.createBindGroup({
+ layout: renderPipeline.getBindGroupLayout(0),
+ entries: bindGroupEntries,
+ });
+
+ const placeholderColorTexture = this.device.createTexture({
+ size: [storageTexture.width, storageTexture.height, 1],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ format: 'rgba8unorm',
+ });
+ this.trackForCleanup(placeholderColorTexture);
+
+ const renderPassEncoder = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: placeholderColorTexture.createView(),
+ loadOp: 'clear',
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
+ storeOp: 'store',
+ },
+ ],
+ });
+ renderPassEncoder.setPipeline(renderPipeline);
+ renderPassEncoder.setBindGroup(0, bindGroup);
+ renderPassEncoder.draw(storageTexture.width * storageTexture.height);
+ renderPassEncoder.end();
+ break;
+ }
+ }
+
+ this.queue.submit([commandEncoder.finish()]);
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('basic')
+ .desc(
+ `The basic functionality tests for read-only storage textures. In the test we read data from
+ the read-only storage texture, write the data into an output storage buffer, and check if the
+ data in the output storage buffer is exactly what we expect.`
+ )
+ .params(u =>
+ u
+ .combine('format', kColorTextureFormats)
+ .filter(
+ p => p.format === 'bgra8unorm' || kTextureFormatInfo[p.format].color?.storage === true
+ )
+ .combine('shaderStage', kValidShaderStages)
+ .combine('dimension', kTextureDimensions)
+ .combine('depthOrArrayLayers', [1, 2] as const)
+ .unless(p => p.dimension === '1d' && p.depthOrArrayLayers > 1)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.format === 'bgra8unorm') {
+ t.selectDeviceOrSkipTestCase('bgra8unorm-storage');
+ }
+ if (t.isCompatibility) {
+ t.skipIfTextureFormatNotUsableAsStorageTexture(t.params.format);
+ }
+ })
+ .fn(t => {
+ const { format, shaderStage, dimension, depthOrArrayLayers } = t.params;
+
+ const kWidth = 8;
+ const height = dimension === '1d' ? 1 : 8;
+ const storageTexture = t.device.createTexture({
+ format,
+ dimension,
+ size: [kWidth, height, depthOrArrayLayers],
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.STORAGE_BINDING,
+ });
+ t.trackForCleanup(storageTexture);
+
+ const expectedData = t.InitTextureAndGetExpectedOutputBufferData(storageTexture, format);
+
+ const outputBuffer = t.device.createBuffer({
+ size: 4 * 4 * kWidth * height * depthOrArrayLayers,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE,
+ });
+ t.trackForCleanup(outputBuffer);
+
+ t.DoTransform(storageTexture, shaderStage, format, outputBuffer);
+
+ switch (kTextureFormatInfo[format].color.type) {
+ case 'uint':
+ t.expectGPUBufferValuesEqual(outputBuffer, new Uint32Array(expectedData));
+ break;
+ case 'sint':
+ t.expectGPUBufferValuesEqual(outputBuffer, new Int32Array(expectedData));
+ break;
+ case 'float':
+ case 'unfilterable-float':
+ t.expectGPUBufferValuesEqual(outputBuffer, new Float32Array(expectedData));
+ break;
+ default:
+ unreachable();
+ break;
+ }
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/storage_texture/read_write.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/storage_texture/read_write.spec.ts
new file mode 100644
index 0000000000..9eb04b2b45
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/storage_texture/read_write.spec.ts
@@ -0,0 +1,385 @@
+export const description = `
+Tests for the behavior of read-write storage textures.
+
+TODO:
+- Test resource usage transitions with read-write storage textures
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { assert, unreachable } from '../../../../common/util/util.js';
+import { kTextureDimensions } from '../../../capability_info.js';
+import { kColorTextureFormats, kTextureFormatInfo } from '../../../format_info.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { align } from '../../../util/math.js';
+
+const kShaderStagesForReadWriteStorageTexture = ['fragment', 'compute'] as const;
+type ShaderStageForReadWriteStorageTexture =
+ (typeof kShaderStagesForReadWriteStorageTexture)[number];
+
+class F extends GPUTest {
+ GetInitialData(storageTexture: GPUTexture): ArrayBuffer {
+ const format = storageTexture.format;
+ const bytesPerBlock = kTextureFormatInfo[format].bytesPerBlock;
+ assert(bytesPerBlock !== undefined);
+
+ const width = storageTexture.width;
+ const height = storageTexture.height;
+ const depthOrArrayLayers = storageTexture.depthOrArrayLayers;
+ const initialData = new ArrayBuffer(bytesPerBlock * width * height * depthOrArrayLayers);
+ const initialTypedData = this.GetTypedArrayBuffer(initialData, format);
+ for (let z = 0; z < depthOrArrayLayers; ++z) {
+ for (let y = 0; y < height; ++y) {
+ for (let x = 0; x < width; ++x) {
+ const index = z * width * height + y * width + x;
+ switch (format) {
+ case 'r32sint':
+ initialTypedData[index] = (index & 1 ? 1 : -1) * (2 * index + 1);
+ break;
+ case 'r32uint':
+ initialTypedData[index] = 2 * index + 1;
+ break;
+ case 'r32float':
+ initialTypedData[index] = (2 * index + 1) / 10.0;
+ break;
+ }
+ }
+ }
+ }
+ return initialData;
+ }
+
+ GetTypedArrayBuffer(arrayBuffer: ArrayBuffer, format: GPUTextureFormat) {
+ switch (format) {
+ case 'r32sint':
+ return new Int32Array(arrayBuffer);
+ case 'r32uint':
+ return new Uint32Array(arrayBuffer);
+ case 'r32float':
+ return new Float32Array(arrayBuffer);
+ default:
+ unreachable();
+ return new Uint8Array(arrayBuffer);
+ }
+ }
+
+ GetExpectedData(
+ shaderStage: ShaderStageForReadWriteStorageTexture,
+ storageTexture: GPUTexture,
+ initialData: ArrayBuffer
+ ): ArrayBuffer {
+ const format = storageTexture.format;
+ const bytesPerBlock = kTextureFormatInfo[format].bytesPerBlock;
+ assert(bytesPerBlock !== undefined);
+
+ const width = storageTexture.width;
+ const height = storageTexture.height;
+ const depthOrArrayLayers = storageTexture.depthOrArrayLayers;
+ const bytesPerRowAlignment = align(bytesPerBlock * width, 256);
+ const itemsPerRow = bytesPerRowAlignment / bytesPerBlock;
+
+ const expectedData = new ArrayBuffer(
+ bytesPerRowAlignment * (height * depthOrArrayLayers - 1) + bytesPerBlock * width
+ );
+ const expectedTypedData = this.GetTypedArrayBuffer(expectedData, format);
+ const initialTypedData = this.GetTypedArrayBuffer(initialData, format);
+ for (let z = 0; z < depthOrArrayLayers; ++z) {
+ for (let y = 0; y < height; ++y) {
+ for (let x = 0; x < width; ++x) {
+ const expectedIndex = z * itemsPerRow * height + y * itemsPerRow + x;
+ switch (shaderStage) {
+ case 'compute': {
+ // In the compute shader we flip the texture along the diagonal.
+ const initialIndex =
+ (depthOrArrayLayers - 1 - z) * width * height +
+ (height - 1 - y) * width +
+ (width - 1 - x);
+ expectedTypedData[expectedIndex] = initialTypedData[initialIndex];
+ break;
+ }
+ case 'fragment': {
+ // In the fragment shader we double the original texel value of the read-write storage
+ // texture.
+ const initialIndex = z * width * height + y * width + x;
+ expectedTypedData[expectedIndex] = initialTypedData[initialIndex] * 2;
+ break;
+ }
+ }
+ }
+ }
+ }
+ return expectedData;
+ }
+
+ RecordCommandsToTransform(
+ device: GPUDevice,
+ shaderStage: ShaderStageForReadWriteStorageTexture,
+ commandEncoder: GPUCommandEncoder,
+ rwTexture: GPUTexture
+ ) {
+ let declaration = '';
+ switch (rwTexture.dimension) {
+ case '1d':
+ declaration = 'texture_storage_1d';
+ break;
+ case '2d':
+ declaration =
+ rwTexture.depthOrArrayLayers > 1 ? 'texture_storage_2d_array' : 'texture_storage_2d';
+ break;
+ case '3d':
+ declaration = 'texture_storage_3d';
+ break;
+ }
+ const textureDeclaration = `
+ @group(0) @binding(0) var rwTexture: ${declaration}<${rwTexture.format}, read_write>;
+ `;
+
+ switch (shaderStage) {
+ case 'fragment': {
+ const vertexShader = `
+ @vertex
+ fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f {
+ var 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));
+ return vec4f(pos[VertexIndex], 0.0, 1.0);
+ }
+ `;
+ let textureLoadStoreCoord = '';
+ switch (rwTexture.dimension) {
+ case '1d':
+ textureLoadStoreCoord = 'textureCoord.x';
+ break;
+ case '2d':
+ textureLoadStoreCoord =
+ rwTexture.depthOrArrayLayers > 1 ? 'textureCoord, z' : 'textureCoord';
+ break;
+ case '3d':
+ textureLoadStoreCoord = 'vec3u(textureCoord, z)';
+ break;
+ }
+ const fragmentShader = `
+ ${textureDeclaration}
+ @fragment
+ fn main(@builtin(position) fragCoord: vec4f) -> @location(0) vec4f {
+ let textureCoord = vec2u(fragCoord.xy);
+
+ for (var z = 0u; z < ${rwTexture.depthOrArrayLayers}; z++) {
+ let initialValue = textureLoad(rwTexture, ${textureLoadStoreCoord});
+ let outputValue = initialValue * 2;
+ textureStore(rwTexture, ${textureLoadStoreCoord}, outputValue);
+ }
+
+ return vec4f(0.0, 1.0, 0.0, 1.0);
+ }
+ `;
+ const renderPipeline = device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: device.createShaderModule({
+ code: vertexShader,
+ }),
+ },
+ fragment: {
+ module: device.createShaderModule({
+ code: fragmentShader,
+ }),
+ targets: [
+ {
+ format: 'rgba8unorm',
+ },
+ ],
+ },
+ primitive: {
+ topology: 'triangle-list',
+ },
+ });
+
+ const bindGroup = device.createBindGroup({
+ layout: renderPipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: rwTexture.createView(),
+ },
+ ],
+ });
+
+ const placeholderColorTexture = device.createTexture({
+ size: [rwTexture.width, rwTexture.height, 1],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ format: 'rgba8unorm',
+ });
+ this.trackForCleanup(placeholderColorTexture);
+
+ const renderPassEncoder = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: placeholderColorTexture.createView(),
+ loadOp: 'clear',
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
+ storeOp: 'store',
+ },
+ ],
+ });
+ renderPassEncoder.setPipeline(renderPipeline);
+ renderPassEncoder.setBindGroup(0, bindGroup);
+ renderPassEncoder.draw(6);
+ renderPassEncoder.end();
+ break;
+ }
+ case 'compute': {
+ let textureLoadCoord = '';
+ let textureStoreCoord = '';
+ switch (rwTexture.dimension) {
+ case '1d':
+ textureLoadCoord = 'dimension - 1u - invocationID.x';
+ textureStoreCoord = 'invocationID.x';
+ break;
+ case '2d':
+ textureLoadCoord =
+ rwTexture.depthOrArrayLayers > 1
+ ? `vec2u(dimension.x - 1u - invocationID.x, dimension.y - 1u - invocationID.y),
+ textureNumLayers(rwTexture) - 1u - invocationID.z`
+ : `vec2u(dimension.x - 1u - invocationID.x, dimension.y - 1u - invocationID.y)`;
+ textureStoreCoord =
+ rwTexture.depthOrArrayLayers > 1
+ ? 'invocationID.xy, invocationID.z'
+ : 'invocationID.xy';
+ break;
+ case '3d':
+ textureLoadCoord = `
+ vec3u(dimension.x - 1u - invocationID.x, dimension.y - 1u - invocationID.y,
+ dimension.z - 1u - invocationID.z)`;
+ textureStoreCoord = 'invocationID';
+ break;
+ }
+
+ const computeShader = `
+ ${textureDeclaration}
+ @compute
+ @workgroup_size(${rwTexture.width}, ${rwTexture.height}, ${rwTexture.depthOrArrayLayers})
+ fn main(@builtin(local_invocation_id) invocationID: vec3u) {
+ let dimension = textureDimensions(rwTexture);
+
+ let initialValue = textureLoad(rwTexture, ${textureLoadCoord});
+ textureBarrier();
+
+ textureStore(rwTexture, ${textureStoreCoord}, initialValue);
+ }`;
+
+ const computePipeline = device.createComputePipeline({
+ compute: {
+ module: device.createShaderModule({
+ code: computeShader,
+ }),
+ },
+ layout: 'auto',
+ });
+ const bindGroup = device.createBindGroup({
+ layout: computePipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: rwTexture.createView(),
+ },
+ ],
+ });
+ const computePassEncoder = commandEncoder.beginComputePass();
+ computePassEncoder.setPipeline(computePipeline);
+ computePassEncoder.setBindGroup(0, bindGroup);
+ computePassEncoder.dispatchWorkgroups(1);
+ computePassEncoder.end();
+ break;
+ }
+ }
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('basic')
+ .desc(
+ `The basic functionality tests for read-write storage textures. In the test we read data from
+ the read-write storage texture, do transforms and write the data back to the read-write storage
+ texture. textureBarrier() is also called in the tests using compute pipelines.`
+ )
+ .params(u =>
+ u
+ .combine('format', kColorTextureFormats)
+ .filter(p => kTextureFormatInfo[p.format].color?.readWriteStorage === true)
+ .combine('shaderStage', kShaderStagesForReadWriteStorageTexture)
+ .combine('textureDimension', kTextureDimensions)
+ .combine('depthOrArrayLayers', [1, 2] as const)
+ .unless(p => p.textureDimension === '1d' && p.depthOrArrayLayers > 1)
+ )
+ .beforeAllSubcases(t => {
+ t.skipIfTextureFormatNotUsableAsStorageTexture(t.params.format);
+ })
+ .fn(t => {
+ const { format, shaderStage, textureDimension, depthOrArrayLayers } = t.params;
+
+ // In compatibility mode the lowest maxComputeInvocationsPerWorkgroup is 128 vs non-compat which is 256
+ // So in non-compat we get 16 * 8 * 2, vs compat where we get 8 * 8 * 2
+ const kWidth = t.isCompatibility ? 8 : 16;
+ const height = textureDimension === '1d' ? 1 : 8;
+ const textureSize = [kWidth, height, depthOrArrayLayers] as const;
+ const storageTexture = t.device.createTexture({
+ format,
+ dimension: textureDimension,
+ size: textureSize,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.STORAGE_BINDING,
+ });
+ t.trackForCleanup(storageTexture);
+
+ const bytesPerBlock = kTextureFormatInfo[format].bytesPerBlock;
+ const initialData = t.GetInitialData(storageTexture);
+ t.queue.writeTexture(
+ { texture: storageTexture },
+ initialData,
+ {
+ bytesPerRow: bytesPerBlock * kWidth,
+ rowsPerImage: height,
+ },
+ textureSize
+ );
+
+ const commandEncoder = t.device.createCommandEncoder();
+
+ t.RecordCommandsToTransform(t.device, shaderStage, commandEncoder, storageTexture);
+
+ const expectedData = t.GetExpectedData(shaderStage, storageTexture, initialData);
+ const readbackBuffer = t.device.createBuffer({
+ size: expectedData.byteLength,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+ });
+ t.trackForCleanup(readbackBuffer);
+ const bytesPerRow = align(bytesPerBlock * kWidth, 256);
+ commandEncoder.copyTextureToBuffer(
+ {
+ texture: storageTexture,
+ },
+ {
+ buffer: readbackBuffer,
+ bytesPerRow,
+ rowsPerImage: height,
+ },
+ textureSize
+ );
+ t.queue.submit([commandEncoder.finish()]);
+
+ switch (format) {
+ case 'r32sint':
+ t.expectGPUBufferValuesEqual(readbackBuffer, new Int32Array(expectedData));
+ break;
+ case 'r32uint':
+ t.expectGPUBufferValuesEqual(readbackBuffer, new Uint32Array(expectedData));
+ break;
+ case 'r32float':
+ t.expectGPUBufferValuesEqual(readbackBuffer, new Float32Array(expectedData));
+ break;
+ }
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/format_reinterpretation.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/format_reinterpretation.spec.ts
index c032415327..f7fb49818e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/format_reinterpretation.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/format_reinterpretation.spec.ts
@@ -100,12 +100,15 @@ g.test('texture_binding')
.combine('format', kRegularTextureFormats)
.combine('viewFormat', kRegularTextureFormats)
.filter(
- ({ format, viewFormat }) => format !== viewFormat && viewCompatible(format, viewFormat)
+ ({ format, viewFormat }) =>
+ format !== viewFormat && viewCompatible(false, format, viewFormat)
)
)
.beforeAllSubcases(t => {
const { format, viewFormat } = t.params;
t.skipIfTextureFormatNotSupported(format, viewFormat);
+ // Compatibility mode does not support format reinterpretation.
+ t.skipIf(t.isCompatibility);
})
.fn(t => {
const { format, viewFormat } = t.params;
@@ -200,13 +203,16 @@ in view format and match in base format.`
.combine('format', kRenderableColorTextureFormats)
.combine('viewFormat', kRenderableColorTextureFormats)
.filter(
- ({ format, viewFormat }) => format !== viewFormat && viewCompatible(format, viewFormat)
+ ({ format, viewFormat }) =>
+ format !== viewFormat && viewCompatible(false, format, viewFormat)
)
.combine('sampleCount', [1, 4])
)
.beforeAllSubcases(t => {
const { format, viewFormat } = t.params;
t.skipIfTextureFormatNotSupported(format, viewFormat);
+ // Compatibility mode does not support format reinterpretation.
+ t.skipIf(t.isCompatibility);
})
.fn(t => {
const { format, viewFormat, sampleCount } = t.params;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/write.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/write.spec.ts
index 0340121334..b4ce6f4cec 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/write.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/write.spec.ts
@@ -1,6 +1,9 @@
export const description = `
Test the result of writing textures through texture views with various options.
+Reads value from a shader array, writes the value via various write methods.
+Check the texture result with the expected texel view.
+
All x= every possible view write method: {
- storage write {fragment, compute}
- render pass store
@@ -13,20 +16,358 @@ TODO: Write helper for this if not already available (see resource_init, buffer_
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
-import { GPUTest } from '../../../gpu_test.js';
+import { unreachable } from '../../../../common/util/util.js';
+import {
+ kRegularTextureFormats,
+ kTextureFormatInfo,
+ RegularTextureFormat,
+} from '../../../format_info.js';
+import { GPUTest, TextureTestMixin } from '../../../gpu_test.js';
+import { kFullscreenQuadVertexShaderCode } from '../../../util/shader.js';
+import { TexelView } from '../../../util/texture/texel_view.js';
+
+export const g = makeTestGroup(TextureTestMixin(GPUTest));
+
+const kTextureViewWriteMethods = [
+ 'storage-write-fragment',
+ 'storage-write-compute',
+ 'render-pass-store',
+ 'render-pass-resolve',
+] as const;
+type TextureViewWriteMethod = (typeof kTextureViewWriteMethods)[number];
+
+// Src color values to read from a shader array.
+const kColorsFloat = [
+ { R: 1.0, G: 0.0, B: 0.0, A: 0.8 },
+ { R: 0.0, G: 1.0, B: 0.0, A: 0.7 },
+ { R: 0.0, G: 0.0, B: 0.0, A: 0.6 },
+ { R: 0.0, G: 0.0, B: 0.0, A: 0.5 },
+ { R: 1.0, G: 1.0, B: 1.0, A: 0.4 },
+ { R: 0.7, G: 0.0, B: 0.0, A: 0.3 },
+ { R: 0.0, G: 0.8, B: 0.0, A: 0.2 },
+ { R: 0.0, G: 0.0, B: 0.9, A: 0.1 },
+ { R: 0.1, G: 0.2, B: 0.0, A: 0.3 },
+ { R: 0.4, G: 0.3, B: 0.6, A: 0.8 },
+];
+
+function FloatToIntColor(c: number) {
+ return Math.floor(c * 100);
+}
+
+const kColorsInt = kColorsFloat.map(c => {
+ return {
+ R: FloatToIntColor(c.R),
+ G: FloatToIntColor(c.G),
+ B: FloatToIntColor(c.B),
+ A: FloatToIntColor(c.A),
+ };
+});
-export const g = makeTestGroup(GPUTest);
+const kTextureSize = 16;
+
+function writeTextureAndGetExpectedTexelView(
+ t: GPUTest,
+ method: TextureViewWriteMethod,
+ view: GPUTextureView,
+ format: RegularTextureFormat,
+ sampleCount: number
+) {
+ const info = kTextureFormatInfo[format];
+ const isFloatType = info.color.type === 'float' || info.color.type === 'unfilterable-float';
+ const kColors = isFloatType ? kColorsFloat : kColorsInt;
+ const expectedTexelView = TexelView.fromTexelsAsColors(
+ format,
+ coords => {
+ const pixelPos = coords.y * kTextureSize + coords.x;
+ return kColors[pixelPos % kColors.length];
+ },
+ { clampToFormatRange: true }
+ );
+ const vecType = isFloatType ? 'vec4f' : info.color.type === 'sint' ? 'vec4i' : 'vec4u';
+ const kColorArrayShaderString = `array<${vecType}, ${kColors.length}>(
+ ${kColors.map(t => `${vecType}(${t.R}, ${t.G}, ${t.B}, ${t.A}) `).join(',')}
+ )`;
+
+ switch (method) {
+ case 'storage-write-compute':
+ {
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var dst: texture_storage_2d<${format}, write>;
+ @compute @workgroup_size(1, 1) fn main(
+ @builtin(global_invocation_id) global_id: vec3<u32>,
+ ) {
+ const src = ${kColorArrayShaderString};
+ let coord = vec2u(global_id.xy);
+ let idx = coord.x + coord.y * ${kTextureSize};
+ textureStore(dst, coord, src[idx % ${kColors.length}]);
+ }`,
+ }),
+ entryPoint: 'main',
+ },
+ });
+ const commandEncoder = t.device.createCommandEncoder();
+ const pass = commandEncoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(
+ 0,
+ t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: view,
+ },
+ ],
+ })
+ );
+ pass.dispatchWorkgroups(kTextureSize, kTextureSize);
+ pass.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+ }
+ break;
+
+ case 'storage-write-fragment':
+ {
+ // Create a placeholder color attachment texture,
+ // The size of which equals that of format texture we are testing,
+ // so that we have the same number of fragments and texels.
+ const kPlaceholderTextureFormat = 'rgba8unorm';
+ const placeholderTexture = t.trackForCleanup(
+ t.device.createTexture({
+ format: kPlaceholderTextureFormat,
+ size: [kTextureSize, kTextureSize],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ })
+ );
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: kFullscreenQuadVertexShaderCode,
+ }),
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var dst: texture_storage_2d<${format}, write>;
+ @fragment fn main(
+ @builtin(position) fragCoord: vec4<f32>,
+ ) {
+ const src = ${kColorArrayShaderString};
+ let coord = vec2u(fragCoord.xy);
+ let idx = coord.x + coord.y * ${kTextureSize};
+ textureStore(dst, coord, src[idx % ${kColors.length}]);
+ }`,
+ }),
+ // Set writeMask to 0 as the fragment shader has no output.
+ targets: [
+ {
+ format: kPlaceholderTextureFormat,
+ writeMask: 0,
+ },
+ ],
+ },
+ });
+ const commandEncoder = t.device.createCommandEncoder();
+ const pass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: placeholderTexture.createView(),
+ loadOp: 'clear',
+ storeOp: 'discard',
+ },
+ ],
+ });
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(
+ 0,
+ t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: view,
+ },
+ ],
+ })
+ );
+ pass.draw(6);
+ pass.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+ }
+ break;
+
+ case 'render-pass-store':
+ case 'render-pass-resolve':
+ {
+ // Create a placeholder color attachment texture for the store target when tesing texture is used as resolve target.
+ const targetView =
+ method === 'render-pass-store'
+ ? view
+ : t
+ .trackForCleanup(
+ t.device.createTexture({
+ format,
+ size: [kTextureSize, kTextureSize],
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount: 4,
+ })
+ )
+ .createView();
+ const resolveView = method === 'render-pass-store' ? undefined : view;
+ const multisampleCount = method === 'render-pass-store' ? sampleCount : 4;
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: kFullscreenQuadVertexShaderCode,
+ }),
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `
+ @fragment fn main(
+ @builtin(position) fragCoord: vec4<f32>,
+ ) -> @location(0) ${vecType} {
+ const src = ${kColorArrayShaderString};
+ let coord = vec2u(fragCoord.xy);
+ let idx = coord.x + coord.y * ${kTextureSize};
+ return src[idx % ${kColors.length}];
+ }`,
+ }),
+ targets: [
+ {
+ format,
+ },
+ ],
+ },
+ multisample: {
+ count: multisampleCount,
+ },
+ });
+ const commandEncoder = t.device.createCommandEncoder();
+ const pass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: targetView,
+ resolveTarget: resolveView,
+ loadOp: 'clear',
+ storeOp: 'store',
+ },
+ ],
+ });
+ pass.setPipeline(pipeline);
+ pass.draw(6);
+ pass.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+ }
+ break;
+ default:
+ unreachable();
+ }
+
+ return expectedTexelView;
+}
g.test('format')
.desc(
`Views of every allowed format.
+Read values from color array in the shader, and write it to the texture view via different write methods.
+
- x= every texture format
- x= sampleCount {1, 4} if valid
- x= every possible view write method (see above)
+
+TODO: Test sampleCount > 1 for 'render-pass-store' after extending copySinglePixelTextureToBufferUsingComputePass
+ to read multiple pixels from multisampled textures. [1]
+TODO: Test rgb10a2uint when TexelRepresentation.numericRange is made per-component. [2]
`
)
- .unimplemented();
+ .params(u =>
+ u //
+ .combine('method', kTextureViewWriteMethods)
+ .combine('format', kRegularTextureFormats)
+ .combine('sampleCount', [1, 4])
+ .filter(({ format, method, sampleCount }) => {
+ const info = kTextureFormatInfo[format];
+
+ if (sampleCount > 1 && !info.multisample) {
+ return false;
+ }
+
+ // [2]
+ if (format === 'rgb10a2uint') {
+ return false;
+ }
+
+ switch (method) {
+ case 'storage-write-compute':
+ case 'storage-write-fragment':
+ return info.color?.storage && sampleCount === 1;
+ case 'render-pass-store':
+ // [1]
+ if (sampleCount > 1) {
+ return false;
+ }
+ return !!info.colorRender;
+ case 'render-pass-resolve':
+ return !!info.colorRender?.resolve && sampleCount === 1;
+ }
+ return true;
+ })
+ )
+ .beforeAllSubcases(t => {
+ const { format, method } = t.params;
+ t.skipIfTextureFormatNotSupported(format);
+
+ switch (method) {
+ case 'storage-write-compute':
+ case 'storage-write-fragment':
+ // Still need to filter again for compat mode.
+ t.skipIfTextureFormatNotUsableAsStorageTexture(format);
+ break;
+ }
+ })
+ .fn(t => {
+ const { format, method, sampleCount } = t.params;
+
+ const usage =
+ GPUTextureUsage.COPY_SRC |
+ (method.includes('storage')
+ ? GPUTextureUsage.STORAGE_BINDING
+ : GPUTextureUsage.RENDER_ATTACHMENT);
+
+ const texture = t.trackForCleanup(
+ t.device.createTexture({
+ format,
+ usage,
+ size: [kTextureSize, kTextureSize],
+ sampleCount,
+ })
+ );
+
+ const view = texture.createView();
+ const expectedTexelView = writeTextureAndGetExpectedTexelView(
+ t,
+ method,
+ view,
+ format,
+ sampleCount
+ );
+
+ // [1] Use copySinglePixelTextureToBufferUsingComputePass to check multisampled texture.
+ t.expectTexelViewComparisonIsOkInTexture({ texture }, expectedTexelView, [
+ kTextureSize,
+ kTextureSize,
+ ]);
+ });
g.test('dimension')
.desc(
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/buffer/mapping.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/buffer/mapping.spec.ts
index 58d7f2767a..250918f2c9 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/buffer/mapping.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/buffer/mapping.spec.ts
@@ -27,8 +27,10 @@ class F extends ValidationTest {
this.expectValidationError(() => {
p = buffer.mapAsync(mode, offset, size);
}, expectation.validationError);
+
let caught = false;
let rejectedEarly = false;
+ let microtaskBRan = false;
// If mapAsync rejected early, microtask A will run before B.
// If not, B will run before A.
p!.catch(() => {
@@ -38,20 +40,31 @@ class F extends ValidationTest {
queueMicrotask(() => {
// Microtask B
rejectedEarly = caught;
+ microtaskBRan = true;
});
- try {
- // This await will always complete after microtasks A and B are both done.
- await p!;
- assert(expectation.rejectName === null, 'mapAsync unexpectedly passed');
- } catch (ex) {
- assert(ex instanceof Error, 'mapAsync rejected with non-error');
- assert(typeof ex.stack === 'string', 'mapAsync rejected without a stack');
- assert(expectation.rejectName === ex.name, `mapAsync rejected unexpectedly with: ${ex}`);
- assert(
- expectation.earlyRejection === rejectedEarly,
- 'mapAsync rejected at an unexpected timing'
- );
- }
+
+ // These handlers should always run after microtasks A and B are both done.
+ await p!.then(
+ () => {
+ unreachable('mapAsync unexpectedly passed');
+ },
+ ex => {
+ const suffix = `\n Rejection: ${ex}`;
+
+ this.expect(microtaskBRan, 'scheduling problem?: microtaskB has not run yet' + suffix);
+ assert(ex instanceof Error, 'mapAsync rejected with non-error' + suffix);
+ this.expect(typeof ex.stack === 'string', 'mapAsync rejected without a stack' + suffix);
+ this.expect(
+ expectation.rejectName === ex.name,
+ 'mapAsync rejected with wrong exception name' + suffix
+ );
+ if (expectation.earlyRejection) {
+ this.expect(rejectedEarly, 'expected early mapAsync rejection, got deferred' + suffix);
+ } else {
+ this.expect(!rejectedEarly, 'expected deferred mapAsync rejection, got early' + suffix);
+ }
+ }
+ );
}
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/features/query_types.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/features/query_types.spec.ts
index 8016252b1e..9d6ab1cdce 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/features/query_types.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/features/query_types.spec.ts
@@ -43,11 +43,13 @@ g.test('createQuerySet')
});
});
-g.test('writeTimestamp')
+g.test('timestamp')
.desc(
`
Tests that writing a timestamp throws a type error exception if the features don't contain
'timestamp-query'.
+
+ TODO: writeTimestamp test is disabled since it's removed from the spec for now.
`
)
.params(u => u.combine('featureContainsTimestampQuery', [false, true]))
@@ -66,11 +68,53 @@ g.test('writeTimestamp')
const querySet = t.device.createQuerySet({
type: featureContainsTimestampQuery ? 'timestamp' : 'occlusion',
- count: 1,
+ count: 2,
});
- const encoder = t.createEncoder('non-pass');
- t.shouldThrow(featureContainsTimestampQuery ? false : 'TypeError', () => {
- encoder.encoder.writeTimestamp(querySet, 0);
- });
+ {
+ let expected = featureContainsTimestampQuery ? false : 'TypeError';
+ // writeTimestamp no longer exists and this should always TypeError.
+ expected = 'TypeError';
+
+ const encoder = t.createEncoder('non-pass');
+ t.shouldThrow(expected, () => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (encoder.encoder as any).writeTimestamp(querySet, 0);
+ });
+ encoder.finish();
+ }
+
+ {
+ const encoder = t.createEncoder('non-pass');
+ encoder.encoder
+ .beginComputePass({
+ timestampWrites: { querySet, beginningOfPassWriteIndex: 0, endOfPassWriteIndex: 1 },
+ })
+ .end();
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !featureContainsTimestampQuery);
+ }
+
+ {
+ const encoder = t.createEncoder('non-pass');
+ const view = t
+ .trackForCleanup(
+ t.device.createTexture({
+ size: [16, 16, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ })
+ )
+ .createView();
+ encoder.encoder
+ .beginRenderPass({
+ colorAttachments: [{ view, loadOp: 'clear', storeOp: 'discard' }],
+ timestampWrites: { querySet, beginningOfPassWriteIndex: 0, endOfPassWriteIndex: 1 },
+ })
+ .end();
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !featureContainsTimestampQuery);
+ }
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/limit_utils.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/limit_utils.ts
index fee2ea716e..2daed5c57c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/limit_utils.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/limit_utils.ts
@@ -45,32 +45,47 @@ export function getPipelineTypeForBindingCombination(bindingCombination: Binding
}
}
-function getBindGroupIndex(bindGroupTest: BindGroupTest, i: number) {
+function getBindGroupIndex(bindGroupTest: BindGroupTest, numBindGroups: number, i: number) {
switch (bindGroupTest) {
case 'sameGroup':
return 0;
case 'differentGroups':
- return i % 3;
+ return i % numBindGroups;
+ }
+}
+
+function getBindingIndex(bindGroupTest: BindGroupTest, numBindGroups: number, i: number) {
+ switch (bindGroupTest) {
+ case 'sameGroup':
+ return i;
+ case 'differentGroups':
+ return (i / numBindGroups) | 0;
}
}
function getWGSLBindings(
- order: ReorderOrder,
- bindGroupTest: BindGroupTest,
- storageDefinitionWGSLSnippetFn: (i: number, j: number) => string,
+ {
+ order,
+ bindGroupTest,
+ storageDefinitionWGSLSnippetFn,
+ numBindGroups,
+ }: {
+ order: ReorderOrder;
+ bindGroupTest: BindGroupTest;
+ storageDefinitionWGSLSnippetFn: (i: number, j: number) => string;
+ numBindGroups: number;
+ },
numBindings: number,
id: number
) {
return reorder(
order,
- range(
- numBindings,
- i =>
- `@group(${getBindGroupIndex(
- bindGroupTest,
- i
- )}) @binding(${i}) ${storageDefinitionWGSLSnippetFn(i, id)};`
- )
+ range(numBindings, i => {
+ const groupNdx = getBindGroupIndex(bindGroupTest, numBindGroups, i);
+ const bindingNdx = getBindingIndex(bindGroupTest, numBindGroups, i);
+ const storageWGSL = storageDefinitionWGSLSnippetFn(i, id);
+ return `@group(${groupNdx}) @binding(${bindingNdx}) ${storageWGSL};`;
+ })
).join('\n ');
}
@@ -80,15 +95,22 @@ export function getPerStageWGSLForBindingCombinationImpl(
bindGroupTest: BindGroupTest,
storageDefinitionWGSLSnippetFn: (i: number, j: number) => string,
bodyFn: (numBindings: number, set: number) => string,
+ numBindGroups: number,
numBindings: number,
extraWGSL = ''
) {
+ const bindingParams = {
+ order,
+ bindGroupTest,
+ storageDefinitionWGSLSnippetFn,
+ numBindGroups,
+ };
switch (bindingCombination) {
case 'vertex':
return `
${extraWGSL}
- ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings, 0)}
+ ${getWGSLBindings(bindingParams, numBindings, 0)}
@vertex fn mainVS() -> @builtin(position) vec4f {
${bodyFn(numBindings, 0)}
@@ -99,7 +121,7 @@ export function getPerStageWGSLForBindingCombinationImpl(
return `
${extraWGSL}
- ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings, 0)}
+ ${getWGSLBindings(bindingParams, numBindings, 0)}
@vertex fn mainVS() -> @builtin(position) vec4f {
return vec4f(0);
@@ -113,9 +135,9 @@ export function getPerStageWGSLForBindingCombinationImpl(
return `
${extraWGSL}
- ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings, 0)}
+ ${getWGSLBindings(bindingParams, numBindings, 0)}
- ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings - 1, 1)}
+ ${getWGSLBindings(bindingParams, numBindings - 1, 1)}
@vertex fn mainVS() -> @builtin(position) vec4f {
${bodyFn(numBindings, 0)}
@@ -131,9 +153,9 @@ export function getPerStageWGSLForBindingCombinationImpl(
return `
${extraWGSL}
- ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings - 1, 0)}
+ ${getWGSLBindings(bindingParams, numBindings - 1, 0)}
- ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings, 1)}
+ ${getWGSLBindings(bindingParams, numBindings, 1)}
@vertex fn mainVS() -> @builtin(position) vec4f {
${bodyFn(numBindings - 1, 0)}
@@ -148,8 +170,7 @@ export function getPerStageWGSLForBindingCombinationImpl(
case 'compute':
return `
${extraWGSL}
- ${getWGSLBindings(order, bindGroupTest, storageDefinitionWGSLSnippetFn, numBindings, 0)}
- @group(3) @binding(0) var<storage, read_write> d: f32;
+ ${getWGSLBindings(bindingParams, numBindings, 0)}
@compute @workgroup_size(1) fn main() {
${bodyFn(numBindings, 0)}
}
@@ -164,6 +185,7 @@ export function getPerStageWGSLForBindingCombination(
bindGroupTest: BindGroupTest,
storageDefinitionWGSLSnippetFn: (i: number, j: number) => string,
usageWGSLSnippetFn: (i: number, j: number) => string,
+ maxBindGroups: number,
numBindings: number,
extraWGSL = ''
) {
@@ -174,6 +196,7 @@ export function getPerStageWGSLForBindingCombination(
storageDefinitionWGSLSnippetFn,
(numBindings: number, set: number) =>
`${range(numBindings, i => usageWGSLSnippetFn(i, set)).join('\n ')}`,
+ maxBindGroups,
numBindings,
extraWGSL
);
@@ -185,6 +208,7 @@ export function getPerStageWGSLForBindingCombinationStorageTextures(
bindGroupTest: BindGroupTest,
storageDefinitionWGSLSnippetFn: (i: number, j: number) => string,
usageWGSLSnippetFn: (i: number, j: number) => string,
+ numBindGroups: number,
numBindings: number,
extraWGSL = ''
) {
@@ -195,6 +219,7 @@ export function getPerStageWGSLForBindingCombinationStorageTextures(
storageDefinitionWGSLSnippetFn,
(numBindings: number, set: number) =>
`${range(numBindings, i => usageWGSLSnippetFn(i, set)).join('\n ')}`,
+ numBindGroups,
numBindings,
extraWGSL
);
@@ -854,7 +879,7 @@ export class LimitTestsImpl extends GPUTestBase {
/**
* Creates an GPURenderCommandsMixin setup with some initial state.
*/
- _getGPURenderCommandsMixin(encoderType: RenderEncoderType) {
+ #getGPURenderCommandsMixin(encoderType: RenderEncoderType) {
const { device } = this;
switch (encoderType) {
@@ -895,7 +920,7 @@ export class LimitTestsImpl extends GPUTestBase {
});
const encoder = device.createCommandEncoder();
- const mixin = encoder.beginRenderPass({
+ const passEncoder = encoder.beginRenderPass({
colorAttachments: [
{
view: texture.createView(),
@@ -906,10 +931,10 @@ export class LimitTestsImpl extends GPUTestBase {
});
return {
- mixin,
+ passEncoder,
bindGroup,
prep() {
- mixin.end();
+ passEncoder.end();
},
test() {
encoder.finish();
@@ -946,16 +971,16 @@ export class LimitTestsImpl extends GPUTestBase {
],
});
- const mixin = device.createRenderBundleEncoder({
+ const passEncoder = device.createRenderBundleEncoder({
colorFormats: ['rgba8unorm'],
});
return {
- mixin,
+ passEncoder,
bindGroup,
prep() {},
test() {
- mixin.finish();
+ passEncoder.finish();
},
};
break;
@@ -964,17 +989,23 @@ export class LimitTestsImpl extends GPUTestBase {
}
/**
- * Tests a method on GPURenderCommandsMixin
- * The function will be called with the mixin.
+ * Test a method on GPURenderCommandsMixin or GPUBindingCommandsMixin
+ * The function will be called with the passEncoder.
*/
- async testGPURenderCommandsMixin(
+ async testGPURenderAndBindingCommandsMixin(
encoderType: RenderEncoderType,
- fn: ({ mixin }: { mixin: GPURenderCommandsMixin }) => void,
+ fn: ({
+ passEncoder,
+ bindGroup,
+ }: {
+ passEncoder: GPURenderCommandsMixin & GPUBindingCommandsMixin;
+ bindGroup: GPUBindGroup;
+ }) => void,
shouldError: boolean,
msg = ''
) {
- const { mixin, prep, test } = this._getGPURenderCommandsMixin(encoderType);
- fn({ mixin });
+ const { passEncoder, prep, test, bindGroup } = this.#getGPURenderCommandsMixin(encoderType);
+ fn({ passEncoder, bindGroup });
prep();
await this.expectValidationError(test, shouldError, msg);
@@ -983,7 +1014,7 @@ export class LimitTestsImpl extends GPUTestBase {
/**
* Creates GPUBindingCommandsMixin setup with some initial state.
*/
- _getGPUBindingCommandsMixin(encoderType: EncoderType) {
+ #getGPUBindingCommandsMixin(encoderType: EncoderType) {
const { device } = this;
switch (encoderType) {
@@ -1016,12 +1047,12 @@ export class LimitTestsImpl extends GPUTestBase {
});
const encoder = device.createCommandEncoder();
- const mixin = encoder.beginComputePass();
+ const passEncoder = encoder.beginComputePass();
return {
- mixin,
+ passEncoder,
bindGroup,
prep() {
- mixin.end();
+ passEncoder.end();
},
test() {
encoder.finish();
@@ -1030,24 +1061,24 @@ export class LimitTestsImpl extends GPUTestBase {
break;
}
case 'render':
- return this._getGPURenderCommandsMixin('render');
+ return this.#getGPURenderCommandsMixin('render');
case 'renderBundle':
- return this._getGPURenderCommandsMixin('renderBundle');
+ return this.#getGPURenderCommandsMixin('renderBundle');
}
}
/**
* Tests a method on GPUBindingCommandsMixin
- * The function pass will be called with the mixin and a bindGroup
+ * The function pass will be called with the passEncoder and a bindGroup
*/
async testGPUBindingCommandsMixin(
encoderType: EncoderType,
- fn: ({ bindGroup }: { mixin: GPUBindingCommandsMixin; bindGroup: GPUBindGroup }) => void,
+ fn: ({ bindGroup }: { passEncoder: GPUBindingCommandsMixin; bindGroup: GPUBindGroup }) => void,
shouldError: boolean,
msg = ''
) {
- const { mixin, bindGroup, prep, test } = this._getGPUBindingCommandsMixin(encoderType);
- fn({ mixin, bindGroup });
+ const { passEncoder, bindGroup, prep, test } = this.#getGPUBindingCommandsMixin(encoderType);
+ fn({ passEncoder, bindGroup });
prep();
await this.expectValidationError(test, shouldError, msg);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxBindGroups.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxBindGroups.spec.ts
index 334b49cc90..166b40ff2c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxBindGroups.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxBindGroups.spec.ts
@@ -1,4 +1,4 @@
-import { range } from '../../../../../common/util/util.js';
+import { assert } from '../../../../../common/util/util.js';
import {
kCreatePipelineTypes,
@@ -10,30 +10,152 @@ import {
const limit = 'maxBindGroups';
export const { g, description } = makeLimitTestGroup(limit);
+type BindingLayout = {
+ buffer?: GPUBufferBindingLayout;
+ sampler?: GPUSamplerBindingLayout;
+ texture?: GPUTextureBindingLayout;
+ storageTexture?: GPUStorageTextureBindingLayout;
+ externalTexture?: GPUExternalTextureBindingLayout;
+};
+
+type LimitToBindingLayout = {
+ name: keyof GPUSupportedLimits;
+ entry: BindingLayout;
+};
+
+const kLimitToBindingLayout: readonly LimitToBindingLayout[] = [
+ {
+ name: 'maxSampledTexturesPerShaderStage',
+ entry: {
+ texture: {},
+ },
+ },
+ {
+ name: 'maxSamplersPerShaderStage',
+ entry: {
+ sampler: {},
+ },
+ },
+ {
+ name: 'maxUniformBuffersPerShaderStage',
+ entry: {
+ buffer: {},
+ },
+ },
+ {
+ name: 'maxStorageBuffersPerShaderStage',
+ entry: {
+ buffer: {
+ type: 'read-only-storage',
+ },
+ },
+ },
+ {
+ name: 'maxStorageTexturesPerShaderStage',
+ entry: {
+ storageTexture: {
+ access: 'write-only',
+ format: 'rgba8unorm',
+ viewDimension: '2d',
+ },
+ },
+ },
+] as const;
+
+/**
+ * Yields all possible binding layout entries for a stage.
+ */
+function* getBindingLayoutEntriesForStage(device: GPUDevice) {
+ for (const { name, entry } of kLimitToBindingLayout) {
+ const limit = device.limits[name] as number;
+ for (let i = 0; i < limit; ++i) {
+ yield entry;
+ }
+ }
+}
+
+/**
+ * Yields all of the possible BindingLayoutEntryAndVisibility entries for a render pipeline
+ */
+function* getBindingLayoutEntriesForRenderPipeline(
+ device: GPUDevice
+): Generator<GPUBindGroupLayoutEntry> {
+ const visibilities = [GPUShaderStage.VERTEX, GPUShaderStage.FRAGMENT];
+ for (const visibility of visibilities) {
+ for (const bindEntryResourceType of getBindingLayoutEntriesForStage(device)) {
+ const entry: GPUBindGroupLayoutEntry = {
+ binding: 0,
+ visibility,
+ ...bindEntryResourceType,
+ };
+ yield entry;
+ }
+ }
+}
+
+/**
+ * Returns the total possible bindings per render pipeline
+ */
+function getTotalPossibleBindingsPerRenderPipeline(device: GPUDevice) {
+ const totalPossibleBindingsPerStage =
+ device.limits.maxSampledTexturesPerShaderStage +
+ device.limits.maxSamplersPerShaderStage +
+ device.limits.maxUniformBuffersPerShaderStage +
+ device.limits.maxStorageBuffersPerShaderStage +
+ device.limits.maxStorageTexturesPerShaderStage;
+ return totalPossibleBindingsPerStage * 2;
+}
+
+/**
+ * Yields count GPUBindGroupLayoutEntries
+ */
+function* getBindingLayoutEntries(
+ device: GPUDevice,
+ count: number
+): Generator<GPUBindGroupLayoutEntry> {
+ assert(count < getTotalPossibleBindingsPerRenderPipeline(device));
+ const iter = getBindingLayoutEntriesForRenderPipeline(device);
+ for (; count > 0; --count) {
+ yield iter.next().value;
+ }
+}
+
g.test('createPipelineLayout,at_over')
.desc(`Test using createPipelineLayout at and over ${limit} limit`)
.params(kMaximumLimitBaseParams)
.fn(async t => {
const { limitTest, testValueName } = t.params;
+
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
- async ({ device, testValue, shouldError }) => {
- const bindGroupLayouts = range(testValue, _i =>
- device.createBindGroupLayout({
- entries: [
- {
- binding: 0,
- visibility: GPUShaderStage.VERTEX,
- buffer: {},
- },
- ],
- })
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const totalPossibleBindingsPerPipeline = getTotalPossibleBindingsPerRenderPipeline(device);
+ // Not sure what to do if we ever hit this but I think it's better to assert than silently skip.
+ assert(
+ testValue < totalPossibleBindingsPerPipeline,
+ `not enough possible bindings(${totalPossibleBindingsPerPipeline}) to test ${testValue} bindGroups`
);
- await t.expectValidationError(() => {
- device.createPipelineLayout({ bindGroupLayouts });
- }, shouldError);
+ const bindingDescriptions: string[] = [];
+ const bindGroupLayouts = [...getBindingLayoutEntries(device, testValue)].map(entry => {
+ bindingDescriptions.push(
+ `${JSON.stringify(entry)} // group(${bindingDescriptions.length})`
+ );
+ return device.createBindGroupLayout({
+ entries: [entry],
+ });
+ });
+
+ await t.expectValidationError(
+ () => {
+ device.createPipelineLayout({ bindGroupLayouts });
+ },
+ shouldError,
+ `testing ${testValue} bindGroups on maxBindGroups = ${actualLimit} with \n${bindingDescriptions.join(
+ '\n'
+ )}`
+ );
}
);
});
@@ -76,8 +198,8 @@ g.test('setBindGroup,at_over')
const lastIndex = testValue - 1;
await t.testGPUBindingCommandsMixin(
encoderType,
- ({ mixin, bindGroup }) => {
- mixin.setBindGroup(lastIndex, bindGroup);
+ ({ passEncoder, bindGroup }) => {
+ passEncoder.setBindGroup(lastIndex, bindGroup);
},
shouldError,
`shouldError: ${shouldError}, actualLimit: ${actualLimit}, testValue: ${lastIndex}`
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxBindGroupsPlusVertexBuffers.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxBindGroupsPlusVertexBuffers.spec.ts
new file mode 100644
index 0000000000..d75ef0ce7d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxBindGroupsPlusVertexBuffers.spec.ts
@@ -0,0 +1,301 @@
+import {
+ kRenderEncoderTypes,
+ kMaximumLimitBaseParams,
+ makeLimitTestGroup,
+ LimitsRequest,
+} from './limit_utils.js';
+
+const kVertexBufferBindGroupPreferences = ['vertexBuffers', 'bindGroups'] as const;
+type VertexBufferBindGroupPreference = (typeof kVertexBufferBindGroupPreferences)[number];
+
+const kLayoutTypes = ['auto', 'explicit'] as const;
+type LayoutType = (typeof kLayoutTypes)[number];
+
+/**
+ * Given testValue, choose more vertex buffers or more bind groups based on preference
+ */
+function getNumBindGroupsAndNumVertexBuffers(
+ device: GPUDevice,
+ preference: VertexBufferBindGroupPreference,
+ testValue: number
+) {
+ switch (preference) {
+ case 'bindGroups': {
+ const numBindGroups = Math.min(testValue, device.limits.maxBindGroups);
+ const numVertexBuffers = Math.max(0, testValue - numBindGroups);
+ return { numVertexBuffers, numBindGroups };
+ }
+ case 'vertexBuffers': {
+ const numVertexBuffers = Math.min(testValue, device.limits.maxVertexBuffers);
+ const numBindGroups = Math.max(0, testValue - numVertexBuffers);
+ return { numVertexBuffers, numBindGroups };
+ }
+ }
+}
+
+function createLayout(device: GPUDevice, layoutType: LayoutType, numBindGroups: number) {
+ switch (layoutType) {
+ case 'auto':
+ return 'auto';
+ case 'explicit': {
+ const bindGroupLayouts = new Array(numBindGroups);
+ if (numBindGroups > 0) {
+ bindGroupLayouts.fill(device.createBindGroupLayout({ entries: [] }));
+ bindGroupLayouts[numBindGroups - 1] = device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.VERTEX,
+ buffer: {},
+ },
+ ],
+ });
+ }
+ return device.createPipelineLayout({ bindGroupLayouts });
+ }
+ }
+}
+
+/**
+ * Generate a render pipeline that can be used to test maxBindGroupsPlusVertexBuffers
+ */
+function getPipelineDescriptor(
+ device: GPUDevice,
+ preference: VertexBufferBindGroupPreference,
+ testValue: number,
+ layoutType: LayoutType
+) {
+ // Get the numVertexBuffers and numBindGroups we could use given testValue as a total.
+ // We will only use 1 of each but we'll use the last index.
+ const { numVertexBuffers, numBindGroups } = getNumBindGroupsAndNumVertexBuffers(
+ device,
+ preference,
+ testValue
+ );
+
+ const layout = createLayout(device, layoutType, numBindGroups);
+
+ const [bindGroupDecl, bindGroupUsage] =
+ numBindGroups === 0
+ ? ['', '']
+ : [`@group(${numBindGroups - 1}) @binding(0) var<uniform> u: f32;`, `_ = u;`];
+
+ const [attribDecl, attribUsage] =
+ numVertexBuffers === 0
+ ? ['', '']
+ : ['@location(0) v: vec4f', `_ = v; // will use vertex buffer ${numVertexBuffers - 1}`];
+
+ const code = `
+ ${bindGroupDecl}
+
+ @vertex fn vs(${attribDecl}) -> @builtin(position) vec4f {
+ ${bindGroupUsage}
+ ${attribUsage}
+ return vec4f(0);
+ }
+
+ @fragment fn fs() -> @location(0) vec4f {
+ return vec4f(0);
+ }
+ `;
+
+ const module = device.createShaderModule({ code });
+ const buffers = new Array<GPUVertexBufferLayout | null>(numVertexBuffers);
+ if (numVertexBuffers > 0) {
+ buffers[numVertexBuffers - 1] = {
+ arrayStride: 16,
+ attributes: [{ shaderLocation: 0, offset: 0, format: 'float32' }],
+ };
+ }
+
+ return {
+ code,
+ descriptor: {
+ layout,
+ vertex: {
+ module,
+ buffers,
+ },
+ fragment: {
+ module,
+ targets: [{ format: 'rgba8unorm' }],
+ },
+ } as const,
+ };
+}
+
+const kExtraLimits: LimitsRequest = {
+ maxBindGroups: 'adapterLimit',
+ maxVertexBuffers: 'adapterLimit',
+};
+
+const limit = 'maxBindGroupsPlusVertexBuffers';
+export const { g, description } = makeLimitTestGroup(limit);
+
+g.test('createRenderPipeline,at_over')
+ .desc(`Test using at and over ${limit} limit in createRenderPipeline(Async).`)
+ .params(
+ kMaximumLimitBaseParams
+ .combine('async', [false, true])
+ .beginSubcases()
+ .combine('preference', kVertexBufferBindGroupPreferences)
+ .combine('layoutType', kLayoutTypes)
+ )
+ .fn(async t => {
+ const { limitTest, testValueName, async, preference, layoutType } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const maxUsableBindGroupsPlusVertexBuffers =
+ device.limits.maxBindGroups + device.limits.maxVertexBuffers;
+ t.skipIf(
+ maxUsableBindGroupsPlusVertexBuffers < actualLimit,
+ `can not test because the max usable bindGroups + vertexBuffers (${maxUsableBindGroupsPlusVertexBuffers}) is < maxBindGroupsAndVertexBuffers (${actualLimit})`
+ );
+ t.skipIf(
+ maxUsableBindGroupsPlusVertexBuffers === actualLimit && testValue > actualLimit,
+ `can not test because the max usable bindGroups + vertexBuffers (${maxUsableBindGroupsPlusVertexBuffers}) === maxBindGroupsAndVertexBuffers (${actualLimit})
+ but the testValue (${testValue}) > maxBindGroupsAndVertexBuffers (${actualLimit})`
+ );
+
+ const { code, descriptor } = getPipelineDescriptor(
+ device,
+ preference,
+ testValue,
+ layoutType
+ );
+
+ await t.testCreateRenderPipeline(
+ descriptor,
+ async,
+ shouldError,
+ `testValue: ${testValue}, actualLimit: ${actualLimit}, shouldError: ${shouldError}\n${code}`
+ );
+ },
+ kExtraLimits
+ );
+ });
+
+g.test('draw,at_over')
+ .desc(`Test using at and over ${limit} limit draw/drawIndexed/drawIndirect/drawIndexedIndirect`)
+ .params(
+ kMaximumLimitBaseParams
+ .combine('encoderType', kRenderEncoderTypes)
+ .beginSubcases()
+ .combine('preference', kVertexBufferBindGroupPreferences)
+ .combine('drawType', ['draw', 'drawIndexed', 'drawIndirect', 'drawIndexedIndirect'] as const)
+ )
+ .fn(async t => {
+ const { limitTest, testValueName, encoderType, drawType, preference } = t.params;
+ await t.testDeviceWithRequestedMaximumLimits(
+ limitTest,
+ testValueName,
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const maxUsableVertexBuffers = Math.min(
+ device.limits.maxVertexBuffers,
+ device.limits.maxVertexAttributes
+ );
+ const maxUsableBindGroupsPlusVertexBuffers =
+ device.limits.maxBindGroups + maxUsableVertexBuffers;
+ t.skipIf(
+ maxUsableBindGroupsPlusVertexBuffers < actualLimit,
+ `can not test because the max usable bindGroups + vertexBuffers (${maxUsableBindGroupsPlusVertexBuffers}) is < maxBindGroupsAndVertexBuffers (${actualLimit})`
+ );
+ t.skipIf(
+ maxUsableBindGroupsPlusVertexBuffers === actualLimit && testValue > actualLimit,
+ `can not test because the max usable bindGroups + vertexBuffers (${maxUsableBindGroupsPlusVertexBuffers}) === maxBindGroupsAndVertexBuffers (${actualLimit})
+ but the testValue (${testValue}) > maxBindGroupsAndVertexBuffers (${actualLimit})`
+ );
+
+ // Get the numVertexBuffers and numBindGroups we could use given testValue as a total.
+ // We will only use 1 of each but we'll use the last index.
+ const { numVertexBuffers, numBindGroups } = getNumBindGroupsAndNumVertexBuffers(
+ device,
+ preference,
+ testValue
+ );
+
+ const module = device.createShaderModule({
+ code: `
+ @vertex fn vs() -> @builtin(position) vec4f {
+ return vec4f(0);
+ }
+ @fragment fn fs() -> @location(0) vec4f {
+ return vec4f(0);
+ }
+ `,
+ });
+ const pipeline = device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module },
+ fragment: { module, targets: [{ format: 'rgba8unorm' }] },
+ });
+
+ const vertexBuffer = device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.VERTEX,
+ });
+ t.trackForCleanup(vertexBuffer);
+
+ await t.testGPURenderAndBindingCommandsMixin(
+ encoderType,
+ ({ bindGroup, passEncoder }) => {
+ // Set the last vertex buffer and clear it. This should have no effect
+ // unless there is a bug in bookkeeping in the implementation.
+ passEncoder.setVertexBuffer(device.limits.maxVertexBuffers - 1, vertexBuffer);
+ passEncoder.setVertexBuffer(device.limits.maxVertexBuffers - 1, null);
+
+ // Set the last bindGroup and clear it. This should have no effect
+ // unless there is a bug in bookkeeping in the implementation.
+ passEncoder.setBindGroup(device.limits.maxBindGroups - 1, bindGroup);
+ passEncoder.setBindGroup(device.limits.maxBindGroups - 1, null);
+
+ if (numVertexBuffers) {
+ passEncoder.setVertexBuffer(numVertexBuffers - 1, vertexBuffer);
+ }
+
+ if (numBindGroups) {
+ passEncoder.setBindGroup(numBindGroups - 1, bindGroup);
+ }
+
+ passEncoder.setPipeline(pipeline);
+
+ const indirectBuffer = device.createBuffer({
+ size: 20,
+ usage: GPUBufferUsage.INDIRECT,
+ });
+ t.trackForCleanup(indirectBuffer);
+
+ const indexBuffer = device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.INDEX,
+ });
+ t.trackForCleanup(indexBuffer);
+
+ switch (drawType) {
+ case 'draw':
+ passEncoder.draw(0);
+ break;
+ case 'drawIndexed':
+ passEncoder.setIndexBuffer(indexBuffer, 'uint16');
+ passEncoder.drawIndexed(0);
+ break;
+ case 'drawIndirect':
+ passEncoder.drawIndirect(indirectBuffer, 0);
+ break;
+ case 'drawIndexedIndirect':
+ passEncoder.setIndexBuffer(indexBuffer, 'uint16');
+ passEncoder.drawIndexedIndirect(indirectBuffer, 0);
+ break;
+ }
+ },
+ shouldError,
+ `testValue: ${testValue}, actualLimit: ${actualLimit}, shouldError: ${shouldError}`
+ );
+
+ vertexBuffer.destroy();
+ },
+ kExtraLimits
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupStorageSize.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupStorageSize.spec.ts
index cb26e18ebe..8a9176983d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupStorageSize.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxComputeWorkgroupStorageSize.spec.ts
@@ -12,7 +12,8 @@ import {
const limit = 'maxComputeWorkgroupStorageSize';
export const { g, description } = makeLimitTestGroup(limit);
-const kSmallestWorkgroupVarSize = 4;
+// Each var is roundUp(16, SizeOf(T))
+const kSmallestWorkgroupVarSize = 16;
const wgslF16Types = {
f16: { alignOf: 2, sizeOf: 2, requireF16: true },
@@ -71,7 +72,9 @@ function getModuleForWorkgroupStorageSize(device: GPUDevice, wgslType: WGSLType,
const { sizeOf, alignOf, requireF16 } = wgslTypes[wgslType];
const unitSize = align(sizeOf, alignOf);
const units = Math.floor(size / unitSize);
- const extra = (size - units * unitSize) / kSmallestWorkgroupVarSize;
+ const sizeUsed = align(units * unitSize, 16);
+ const sizeLeft = size - sizeUsed;
+ const extra = Math.floor(sizeLeft / kSmallestWorkgroupVarSize);
const code =
(requireF16 ? 'enable f16;\n' : '') +
@@ -89,7 +92,7 @@ function getModuleForWorkgroupStorageSize(device: GPUDevice, wgslType: WGSLType,
b: vec2f,
};
var<workgroup> d0: array<${wgslType}, ${units}>;
- ${extra ? `var<workgroup> d1: array<f32, ${extra}>;` : ''}
+ ${extra ? `var<workgroup> d1: array<vec4<f32>, ${extra}>;` : ''}
@compute @workgroup_size(1) fn main() {
_ = d0;
${extra ? '_ = d1;' : ''}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxInterStageShaderComponents.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxInterStageShaderComponents.spec.ts
index eeb9eb0faf..409dc72724 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxInterStageShaderComponents.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxInterStageShaderComponents.spec.ts
@@ -111,8 +111,12 @@ g.test('createRenderPipeline,at_over')
.combine('sampleMaskOut', [false, true])
)
.beforeAllSubcases(t => {
- if (t.isCompatibility && (t.params.sampleMaskIn || t.params.sampleMaskOut)) {
- t.skip('sample_mask not supported in compatibility mode');
+ if (t.isCompatibility) {
+ t.skipIf(
+ t.params.sampleMaskIn || t.params.sampleMaskOut,
+ 'sample_mask not supported in compatibility mode'
+ );
+ t.skipIf(t.params.sampleIndex, 'sample_index not supported in compatibility mode');
}
})
.fn(async t => {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSampledTexturesPerShaderStage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSampledTexturesPerShaderStage.spec.ts
index cd90d9d907..57e602c40a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSampledTexturesPerShaderStage.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSampledTexturesPerShaderStage.spec.ts
@@ -3,6 +3,7 @@ import {
reorder,
kReorderOrderKeys,
ReorderOrder,
+ assert,
} from '../../../../../common/util/util.js';
import { kShaderStageCombinationsWithStage } from '../../../../capability_info.js';
@@ -13,8 +14,14 @@ import {
kBindingCombinations,
getPipelineTypeForBindingCombination,
getPerStageWGSLForBindingCombination,
+ LimitsRequest,
} from './limit_utils.js';
+const kExtraLimits: LimitsRequest = {
+ maxBindingsPerBindGroup: 'adapterLimit',
+ maxBindGroups: 'adapterLimit',
+};
+
const limit = 'maxSampledTexturesPerShaderStage';
export const { g, description } = makeLimitTestGroup(limit);
@@ -43,6 +50,9 @@ g.test('createBindGroupLayout,at_over')
Note: We also test order to make sure the implementation isn't just looking
at just the last entry.
+
+ Note: It's also possible the maxBindingsPerBindGroup is lower than
+ ${limit} in which case skip the test since we can not hit the limit.
`
)
.params(
@@ -56,11 +66,17 @@ g.test('createBindGroupLayout,at_over')
limitTest,
testValueName,
async ({ device, testValue, shouldError }) => {
+ t.skipIf(
+ t.adapter.limits.maxBindingsPerBindGroup < testValue,
+ `maxBindingsPerBindGroup = ${t.adapter.limits.maxBindingsPerBindGroup} which is less than ${testValue}`
+ );
+
await t.expectValidationError(
() => createBindGroupLayout(device, visibility, order, testValue),
shouldError
);
- }
+ },
+ kExtraLimits
);
});
@@ -83,18 +99,30 @@ g.test('createPipelineLayout,at_over')
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
- async ({ device, testValue, shouldError }) => {
- const kNumGroups = 3;
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const maxBindingsPerBindGroup = Math.min(
+ t.device.limits.maxBindingsPerBindGroup,
+ actualLimit
+ );
+ const kNumGroups = Math.ceil(testValue / maxBindingsPerBindGroup);
+
+ // Not sure what to do in this case but best we get notified if it happens.
+ assert(kNumGroups <= t.device.limits.maxBindGroups);
+
const bindGroupLayouts = range(kNumGroups, i => {
- const minInGroup = Math.floor(testValue / kNumGroups);
- const numInGroup = i ? minInGroup : testValue - minInGroup * (kNumGroups - 1);
+ const numInGroup = Math.min(
+ testValue - i * maxBindingsPerBindGroup,
+ maxBindingsPerBindGroup
+ );
return createBindGroupLayout(device, visibility, order, numInGroup);
});
+
await t.expectValidationError(
() => device.createPipelineLayout({ bindGroupLayouts }),
shouldError
);
- }
+ },
+ kExtraLimits
);
});
@@ -122,16 +150,21 @@ g.test('createPipeline,at_over')
limitTest,
testValueName,
async ({ device, testValue, actualLimit, shouldError }) => {
+ t.skipIf(
+ bindGroupTest === 'sameGroup' && testValue > device.limits.maxBindingsPerBindGroup,
+ `can not test ${testValue} bindings in same group because maxBindingsPerBindGroup = ${device.limits.maxBindingsPerBindGroup}`
+ );
+
const code = getPerStageWGSLForBindingCombination(
bindingCombination,
order,
bindGroupTest,
(i, j) => `var u${j}_${i}: texture_2d<f32>`,
(i, j) => `_ = textureLoad(u${j}_${i}, vec2u(0), 0);`,
+ device.limits.maxBindGroups,
testValue
);
const module = device.createShaderModule({ code });
-
await t.testCreatePipeline(
pipelineType,
async,
@@ -139,6 +172,7 @@ g.test('createPipeline,at_over')
shouldError,
`actualLimit: ${actualLimit}, testValue: ${testValue}\n:${code}`
);
- }
+ },
+ kExtraLimits
);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSamplersPerShaderStage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSamplersPerShaderStage.spec.ts
index 3103d423c9..892c1f498d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSamplersPerShaderStage.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxSamplersPerShaderStage.spec.ts
@@ -3,6 +3,7 @@ import {
reorder,
kReorderOrderKeys,
ReorderOrder,
+ assert,
} from '../../../../../common/util/util.js';
import { kShaderStageCombinationsWithStage } from '../../../../capability_info.js';
@@ -13,8 +14,14 @@ import {
kBindingCombinations,
getPipelineTypeForBindingCombination,
getPerStageWGSLForBindingCombination,
+ LimitsRequest,
} from './limit_utils.js';
+const kExtraLimits: LimitsRequest = {
+ maxBindingsPerBindGroup: 'adapterLimit',
+ maxBindGroups: 'adapterLimit',
+};
+
const limit = 'maxSamplersPerShaderStage';
export const { g, description } = makeLimitTestGroup(limit);
@@ -56,11 +63,17 @@ g.test('createBindGroupLayout,at_over')
limitTest,
testValueName,
async ({ device, testValue, shouldError }) => {
+ t.skipIf(
+ t.adapter.limits.maxBindingsPerBindGroup < testValue,
+ `maxBindingsPerBindGroup = ${t.adapter.limits.maxBindingsPerBindGroup} which is less than ${testValue}`
+ );
+
await t.expectValidationError(
() => createBindGroupLayout(device, visibility, order, testValue),
shouldError
);
- }
+ },
+ kExtraLimits
);
});
@@ -83,18 +96,29 @@ g.test('createPipelineLayout,at_over')
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
- async ({ device, testValue, shouldError }) => {
- const kNumGroups = 3;
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const maxBindingsPerBindGroup = Math.min(
+ t.device.limits.maxBindingsPerBindGroup,
+ actualLimit
+ );
+ const kNumGroups = Math.ceil(testValue / maxBindingsPerBindGroup);
+
+ // Not sure what to do in this case but best we get notified if it happens.
+ assert(kNumGroups <= t.device.limits.maxBindGroups);
+
const bindGroupLayouts = range(kNumGroups, i => {
- const minInGroup = Math.floor(testValue / kNumGroups);
- const numInGroup = i ? minInGroup : testValue - minInGroup * (kNumGroups - 1);
+ const numInGroup = Math.min(
+ testValue - i * maxBindingsPerBindGroup,
+ maxBindingsPerBindGroup
+ );
return createBindGroupLayout(device, visibility, order, numInGroup);
});
await t.expectValidationError(
() => device.createPipelineLayout({ bindGroupLayouts }),
shouldError
);
- }
+ },
+ kExtraLimits
);
});
@@ -122,14 +146,27 @@ g.test('createPipeline,at_over')
limitTest,
testValueName,
async ({ device, testValue, actualLimit, shouldError }) => {
+ t.skipIf(
+ bindGroupTest === 'sameGroup' && testValue > device.limits.maxBindingsPerBindGroup,
+ `can not test ${testValue} bindings in same group because maxBindingsPerBindGroup = ${device.limits.maxBindingsPerBindGroup}`
+ );
+
+ // If this was false the texture binding would overlap the sampler bindings.
+ assert(testValue < device.limits.maxBindGroups * device.limits.maxBindingsPerBindGroup);
+
+ // Put the texture on the last possible binding.
+ const groupNdx = device.limits.maxBindGroups - 1;
+ const bindingNdx = device.limits.maxBindingsPerBindGroup - 1;
+
const code = getPerStageWGSLForBindingCombination(
bindingCombination,
order,
bindGroupTest,
(i, j) => `var u${j}_${i}: sampler`,
(i, j) => `_ = textureGather(0, tex, u${j}_${i}, vec2f(0));`,
+ device.limits.maxBindGroups,
testValue,
- '@group(3) @binding(1) var tex: texture_2d<f32>;'
+ `@group(${groupNdx}) @binding(${bindingNdx}) var tex: texture_2d<f32>;`
);
const module = device.createShaderModule({ code });
@@ -140,6 +177,7 @@ g.test('createPipeline,at_over')
shouldError,
`actualLimit: ${actualLimit}, testValue: ${testValue}\n:${code}`
);
- }
+ },
+ kExtraLimits
);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageBuffersPerShaderStage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageBuffersPerShaderStage.spec.ts
index 5dfff78907..ee7c3a0246 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageBuffersPerShaderStage.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageBuffersPerShaderStage.spec.ts
@@ -3,7 +3,9 @@ import {
reorder,
kReorderOrderKeys,
ReorderOrder,
+ assert,
} from '../../../../../common/util/util.js';
+import { kShaderStageCombinationsWithStage } from '../../../../capability_info.js';
import { GPUConst } from '../../../../constants.js';
import {
@@ -13,8 +15,14 @@ import {
kBindingCombinations,
getPipelineTypeForBindingCombination,
getPerStageWGSLForBindingCombination,
+ LimitsRequest,
} from './limit_utils.js';
+const kExtraLimits: LimitsRequest = {
+ maxBindingsPerBindGroup: 'adapterLimit',
+ maxBindGroups: 'adapterLimit',
+};
+
const limit = 'maxStorageBuffersPerShaderStage';
export const { g, description } = makeLimitTestGroup(limit);
@@ -48,34 +56,31 @@ g.test('createBindGroupLayout,at_over')
)
.params(
kMaximumLimitBaseParams
- .combine('visibility', [
- GPUConst.ShaderStage.VERTEX,
- GPUConst.ShaderStage.FRAGMENT,
- GPUConst.ShaderStage.VERTEX | GPUConst.ShaderStage.FRAGMENT,
- GPUConst.ShaderStage.COMPUTE,
- GPUConst.ShaderStage.VERTEX | GPUConst.ShaderStage.COMPUTE,
- GPUConst.ShaderStage.FRAGMENT | GPUConst.ShaderStage.COMPUTE,
- GPUConst.ShaderStage.VERTEX | GPUConst.ShaderStage.FRAGMENT | GPUConst.ShaderStage.COMPUTE,
- ])
+ .combine('visibility', kShaderStageCombinationsWithStage)
.combine('type', ['storage', 'read-only-storage'] as GPUBufferBindingType[])
.combine('order', kReorderOrderKeys)
+ .filter(
+ ({ visibility, type }) =>
+ (visibility & GPUConst.ShaderStage.VERTEX) === 0 || type !== 'storage'
+ )
)
.fn(async t => {
const { limitTest, testValueName, visibility, order, type } = t.params;
- if (visibility & GPUConst.ShaderStage.VERTEX && type === 'storage') {
- // vertex stage does not support storage buffers
- return;
- }
-
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
async ({ device, testValue, shouldError }) => {
+ t.skipIf(
+ t.adapter.limits.maxBindingsPerBindGroup < testValue,
+ `maxBindingsPerBindGroup = ${t.adapter.limits.maxBindingsPerBindGroup} which is less than ${testValue}`
+ );
+
await t.expectValidationError(() => {
createBindGroupLayout(device, visibility, type, order, testValue);
}, shouldError);
- }
+ },
+ kExtraLimits
);
});
@@ -90,41 +95,44 @@ g.test('createPipelineLayout,at_over')
)
.params(
kMaximumLimitBaseParams
- .combine('visibility', [
- GPUConst.ShaderStage.VERTEX,
- GPUConst.ShaderStage.FRAGMENT,
- GPUConst.ShaderStage.VERTEX | GPUConst.ShaderStage.FRAGMENT,
- GPUConst.ShaderStage.COMPUTE,
- GPUConst.ShaderStage.VERTEX | GPUConst.ShaderStage.COMPUTE,
- GPUConst.ShaderStage.FRAGMENT | GPUConst.ShaderStage.COMPUTE,
- GPUConst.ShaderStage.VERTEX | GPUConst.ShaderStage.FRAGMENT | GPUConst.ShaderStage.COMPUTE,
- ])
+ .combine('visibility', kShaderStageCombinationsWithStage)
.combine('type', ['storage', 'read-only-storage'] as GPUBufferBindingType[])
.combine('order', kReorderOrderKeys)
+ .filter(
+ ({ visibility, type }) =>
+ (visibility & GPUConst.ShaderStage.VERTEX) === 0 || type !== 'storage'
+ )
)
.fn(async t => {
const { limitTest, testValueName, visibility, order, type } = t.params;
- if (visibility & GPUConst.ShaderStage.VERTEX && type === 'storage') {
- // vertex stage does not support storage buffers
- return;
- }
-
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
- async ({ device, testValue, shouldError }) => {
- const kNumGroups = 3;
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const maxBindingsPerBindGroup = Math.min(
+ t.device.limits.maxBindingsPerBindGroup,
+ actualLimit
+ );
+ const kNumGroups = Math.ceil(testValue / maxBindingsPerBindGroup);
+
+ // Not sure what to do in this case but best we get notified if it happens.
+ assert(kNumGroups <= t.device.limits.maxBindGroups);
+
const bindGroupLayouts = range(kNumGroups, i => {
- const minInGroup = Math.floor(testValue / kNumGroups);
- const numInGroup = i ? minInGroup : testValue - minInGroup * (kNumGroups - 1);
+ const numInGroup = Math.min(
+ testValue - i * maxBindingsPerBindGroup,
+ maxBindingsPerBindGroup
+ );
return createBindGroupLayout(device, visibility, type, order, numInGroup);
});
+
await t.expectValidationError(
() => device.createPipelineLayout({ bindGroupLayouts }),
shouldError
);
- }
+ },
+ kExtraLimits
);
});
@@ -152,12 +160,18 @@ g.test('createPipeline,at_over')
limitTest,
testValueName,
async ({ device, testValue, actualLimit, shouldError }) => {
+ t.skipIf(
+ bindGroupTest === 'sameGroup' && testValue > device.limits.maxBindingsPerBindGroup,
+ `can not test ${testValue} bindings in same group because maxBindingsPerBindGroup = ${device.limits.maxBindingsPerBindGroup}`
+ );
+
const code = getPerStageWGSLForBindingCombination(
bindingCombination,
order,
bindGroupTest,
(i, j) => `var<storage> u${j}_${i}: f32`,
(i, j) => `_ = u${j}_${i};`,
+ device.limits.maxBindGroups,
testValue
);
const module = device.createShaderModule({ code });
@@ -169,6 +183,7 @@ g.test('createPipeline,at_over')
shouldError,
`actualLimit: ${actualLimit}, testValue: ${testValue}\n:${code}`
);
- }
+ },
+ kExtraLimits
);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageTexturesPerShaderStage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageTexturesPerShaderStage.spec.ts
index dee6069b44..8af61f51fc 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageTexturesPerShaderStage.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxStorageTexturesPerShaderStage.spec.ts
@@ -3,6 +3,7 @@ import {
reorder,
ReorderOrder,
kReorderOrderKeys,
+ assert,
} from '../../../../../common/util/util.js';
import { GPUConst } from '../../../../constants.js';
@@ -13,8 +14,14 @@ import {
getPerStageWGSLForBindingCombinationStorageTextures,
getPipelineTypeForBindingCombination,
BindingCombination,
+ LimitsRequest,
} from './limit_utils.js';
+const kExtraLimits: LimitsRequest = {
+ maxBindingsPerBindGroup: 'adapterLimit',
+ maxBindGroups: 'adapterLimit',
+};
+
const limit = 'maxStorageTexturesPerShaderStage';
export const { g, description } = makeLimitTestGroup(limit);
@@ -60,11 +67,17 @@ g.test('createBindGroupLayout,at_over')
limitTest,
testValueName,
async ({ device, testValue, shouldError }) => {
+ t.skipIf(
+ t.adapter.limits.maxBindingsPerBindGroup < testValue,
+ `maxBindingsPerBindGroup = ${t.adapter.limits.maxBindingsPerBindGroup} which is less than ${testValue}`
+ );
+
await t.expectValidationError(
() => createBindGroupLayout(device, visibility, order, testValue),
shouldError
);
- }
+ },
+ kExtraLimits
);
});
@@ -91,18 +104,30 @@ g.test('createPipelineLayout,at_over')
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
- async ({ device, testValue, shouldError }) => {
- const kNumGroups = 3;
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const maxBindingsPerBindGroup = Math.min(
+ t.device.limits.maxBindingsPerBindGroup,
+ actualLimit
+ );
+ const kNumGroups = Math.ceil(testValue / maxBindingsPerBindGroup);
+
+ // Not sure what to do in this case but best we get notified if it happens.
+ assert(kNumGroups <= t.device.limits.maxBindGroups);
+
const bindGroupLayouts = range(kNumGroups, i => {
- const minInGroup = Math.floor(testValue / kNumGroups);
- const numInGroup = i ? minInGroup : testValue - minInGroup * (kNumGroups - 1);
+ const numInGroup = Math.min(
+ testValue - i * maxBindingsPerBindGroup,
+ maxBindingsPerBindGroup
+ );
return createBindGroupLayout(device, visibility, order, numInGroup);
});
+
await t.expectValidationError(
() => device.createPipelineLayout({ bindGroupLayouts }),
shouldError
);
- }
+ },
+ kExtraLimits
);
});
@@ -130,6 +155,11 @@ g.test('createPipeline,at_over')
limitTest,
testValueName,
async ({ device, testValue, actualLimit, shouldError }) => {
+ t.skipIf(
+ bindGroupTest === 'sameGroup' && testValue > device.limits.maxBindingsPerBindGroup,
+ `can not test ${testValue} bindings in same group because maxBindingsPerBindGroup = ${device.limits.maxBindingsPerBindGroup}`
+ );
+
if (bindingCombination === 'fragment') {
return;
}
@@ -140,6 +170,7 @@ g.test('createPipeline,at_over')
bindGroupTest,
(i, j) => `var u${j}_${i}: texture_storage_2d<rgba8unorm, write>`,
(i, j) => `textureStore(u${j}_${i}, vec2u(0), vec4f(1));`,
+ device.limits.maxBindGroups,
testValue
);
const module = device.createShaderModule({ code });
@@ -151,6 +182,7 @@ g.test('createPipeline,at_over')
shouldError,
`actualLimit: ${actualLimit}, testValue: ${testValue}\n:${code}`
);
- }
+ },
+ kExtraLimits
);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxUniformBuffersPerShaderStage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxUniformBuffersPerShaderStage.spec.ts
index 7e55078f16..64de1a71f6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxUniformBuffersPerShaderStage.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxUniformBuffersPerShaderStage.spec.ts
@@ -3,6 +3,7 @@ import {
reorder,
kReorderOrderKeys,
ReorderOrder,
+ assert,
} from '../../../../../common/util/util.js';
import { kShaderStageCombinationsWithStage } from '../../../../capability_info.js';
@@ -13,8 +14,14 @@ import {
kBindingCombinations,
getPipelineTypeForBindingCombination,
getPerStageWGSLForBindingCombination,
+ LimitsRequest,
} from './limit_utils.js';
+const kExtraLimits: LimitsRequest = {
+ maxBindingsPerBindGroup: 'adapterLimit',
+ maxBindGroups: 'adapterLimit',
+};
+
const limit = 'maxUniformBuffersPerShaderStage';
export const { g, description } = makeLimitTestGroup(limit);
@@ -56,11 +63,17 @@ g.test('createBindGroupLayout,at_over')
limitTest,
testValueName,
async ({ device, testValue, shouldError }) => {
+ t.skipIf(
+ t.adapter.limits.maxBindingsPerBindGroup < testValue,
+ `maxBindingsPerBindGroup = ${t.adapter.limits.maxBindingsPerBindGroup} which is less than ${testValue}`
+ );
+
await t.expectValidationError(
() => createBindGroupLayout(device, visibility, order, testValue),
shouldError
);
- }
+ },
+ kExtraLimits
);
});
@@ -83,18 +96,30 @@ g.test('createPipelineLayout,at_over')
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
- async ({ device, testValue, shouldError }) => {
- const kNumGroups = 3;
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const maxBindingsPerBindGroup = Math.min(
+ t.device.limits.maxBindingsPerBindGroup,
+ actualLimit
+ );
+ const kNumGroups = Math.ceil(testValue / maxBindingsPerBindGroup);
+
+ // Not sure what to do in this case but best we get notified if it happens.
+ assert(kNumGroups <= t.device.limits.maxBindGroups);
+
const bindGroupLayouts = range(kNumGroups, i => {
- const minInGroup = Math.floor(testValue / kNumGroups);
- const numInGroup = i ? minInGroup : testValue - minInGroup * (kNumGroups - 1);
+ const numInGroup = Math.min(
+ testValue - i * maxBindingsPerBindGroup,
+ maxBindingsPerBindGroup
+ );
return createBindGroupLayout(device, visibility, order, numInGroup);
});
+
await t.expectValidationError(
() => device.createPipelineLayout({ bindGroupLayouts }),
shouldError
);
- }
+ },
+ kExtraLimits
);
});
@@ -122,12 +147,18 @@ g.test('createPipeline,at_over')
limitTest,
testValueName,
async ({ device, testValue, actualLimit, shouldError }) => {
+ t.skipIf(
+ bindGroupTest === 'sameGroup' && testValue > device.limits.maxBindingsPerBindGroup,
+ `can not test ${testValue} bindings in same group because maxBindingsPerBindGroup = ${device.limits.maxBindingsPerBindGroup}`
+ );
+
const code = getPerStageWGSLForBindingCombination(
bindingCombination,
order,
bindGroupTest,
(i, j) => `var<uniform> u${j}_${i}: f32`,
(i, j) => `_ = u${j}_${i};`,
+ device.limits.maxBindGroups,
testValue
);
const module = device.createShaderModule({ code });
@@ -139,6 +170,7 @@ g.test('createPipeline,at_over')
shouldError,
`actualLimit: ${actualLimit}, testValue: ${testValue}\n:${code}`
);
- }
+ },
+ kExtraLimits
);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxVertexBuffers.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxVertexBuffers.spec.ts
index 7f760fe9b6..51cb44d55b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxVertexBuffers.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/capability_checks/limits/maxVertexBuffers.spec.ts
@@ -1,41 +1,23 @@
-import { range } from '../../../../../common/util/util.js';
-
import { kRenderEncoderTypes, kMaximumLimitBaseParams, makeLimitTestGroup } from './limit_utils.js';
-const kPipelineTypes = ['withoutLocations', 'withLocations'] as const;
-type PipelineType = (typeof kPipelineTypes)[number];
+function getPipelineDescriptor(device: GPUDevice, testValue: number): GPURenderPipelineDescriptor {
+ const module = device.createShaderModule({
+ code: `
+ @vertex fn vs(@location(0) p: vec4f) -> @builtin(position) vec4f {
+ return p;
+ }`,
+ });
+ const buffers = new Array<GPUVertexBufferLayout>(testValue);
+ buffers[testValue - 1] = {
+ arrayStride: 16,
+ attributes: [{ shaderLocation: 0, offset: 0, format: 'float32' }],
+ };
-function getPipelineDescriptor(
- device: GPUDevice,
- pipelineType: PipelineType,
- testValue: number
-): GPURenderPipelineDescriptor {
- const code =
- pipelineType === 'withLocations'
- ? `
- struct VSInput {
- ${range(testValue, i => `@location(${i}) p${i}: f32,`).join('\n')}
- }
- @vertex fn vs(v: VSInput) -> @builtin(position) vec4f {
- let x = ${range(testValue, i => `v.p${i}`).join(' + ')};
- return vec4f(x, 0, 0, 1);
- }
- `
- : `
- @vertex fn vs() -> @builtin(position) vec4f {
- return vec4f(0);
- }
- `;
- const module = device.createShaderModule({ code });
return {
layout: 'auto',
vertex: {
module,
- entryPoint: 'vs',
- buffers: range(testValue, i => ({
- arrayStride: 32,
- attributes: [{ shaderLocation: i, offset: 0, format: 'float32' }],
- })),
+ buffers,
},
};
}
@@ -45,18 +27,22 @@ export const { g, description } = makeLimitTestGroup(limit);
g.test('createRenderPipeline,at_over')
.desc(`Test using at and over ${limit} limit in createRenderPipeline(Async)`)
- .params(
- kMaximumLimitBaseParams.combine('async', [false, true]).combine('pipelineType', kPipelineTypes)
- )
+ .params(kMaximumLimitBaseParams.combine('async', [false, true]))
.fn(async t => {
- const { limitTest, testValueName, async, pipelineType } = t.params;
+ const { limitTest, testValueName, async } = t.params;
await t.testDeviceWithRequestedMaximumLimits(
limitTest,
testValueName,
- async ({ device, testValue, shouldError }) => {
- const pipelineDescriptor = getPipelineDescriptor(device, pipelineType, testValue);
+ async ({ device, testValue, shouldError, actualLimit }) => {
+ const pipelineDescriptor = getPipelineDescriptor(device, testValue);
+ const lastIndex = testValue - 1;
- await t.testCreateRenderPipeline(pipelineDescriptor, async, shouldError);
+ await t.testCreateRenderPipeline(
+ pipelineDescriptor,
+ async,
+ shouldError,
+ `lastIndex: ${lastIndex}, actualLimit: ${actualLimit}, shouldError: ${shouldError}`
+ );
}
);
});
@@ -77,10 +63,10 @@ g.test('setVertexBuffer,at_over')
usage: GPUBufferUsage.VERTEX,
});
- await t.testGPURenderCommandsMixin(
+ await t.testGPURenderAndBindingCommandsMixin(
encoderType,
- ({ mixin }) => {
- mixin.setVertexBuffer(lastIndex, buffer);
+ ({ passEncoder }) => {
+ passEncoder.setVertexBuffer(lastIndex, buffer);
},
shouldError,
`lastIndex: ${lastIndex}, actualLimit: ${actualLimit}, shouldError: ${shouldError}`
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/compute_pipeline.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/compute_pipeline.spec.ts
index 3a0a51b363..790f25897a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/compute_pipeline.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/compute_pipeline.spec.ts
@@ -5,9 +5,16 @@ Note: entry point matching tests are in shader_module/entry_point.spec.ts
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { keysOf } from '../../../common/util/data_tables.js';
import { kValue } from '../../util/constants.js';
import { TShaderStage, getShaderWithEntryPoint } from '../../util/shader.js';
+import {
+ kAPIResources,
+ getWGSLShaderForResource,
+ getAPIBindGroupLayoutForResource,
+ doResourcesMatch,
+} from './utils.js';
import { ValidationTest } from './validation_test.js';
class F extends ValidationTest {
@@ -690,3 +697,46 @@ Tests calling createComputePipeline(Async) validation for overridable constants
testFn(maxVec4Count + 1, 0, false);
testFn(0, maxMat4Count + 1, false);
});
+
+g.test('resource_compatibility')
+ .desc(
+ 'Tests validation of resource (bind group) compatibility between pipeline layout and WGSL shader'
+ )
+ .params(u =>
+ u //
+ .combine('apiResource', keysOf(kAPIResources))
+ .beginSubcases()
+ .combine('isAsync', [true, false] as const)
+ .combine('wgslResource', keysOf(kAPIResources))
+ )
+ .fn(t => {
+ const apiResource = kAPIResources[t.params.apiResource];
+ const wgslResource = kAPIResources[t.params.wgslResource];
+ t.skipIf(
+ wgslResource.storageTexture !== undefined &&
+ wgslResource.storageTexture.access !== 'write-only' &&
+ !t.hasLanguageFeature('readonly_and_readwrite_storage_textures'),
+ 'Storage textures require language feature'
+ );
+
+ const layout = t.device.createPipelineLayout({
+ bindGroupLayouts: [
+ getAPIBindGroupLayoutForResource(t.device, GPUShaderStage.COMPUTE, apiResource),
+ ],
+ });
+
+ const descriptor = {
+ layout,
+ compute: {
+ module: t.device.createShaderModule({
+ code: getWGSLShaderForResource('compute', wgslResource),
+ }),
+ entryPoint: 'main',
+ },
+ };
+ t.doCreateComputePipelineTest(
+ t.params.isAsync,
+ doResourcesMatch(apiResource, wgslResource),
+ descriptor
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroup.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroup.spec.ts
index ddd0f8b39f..b2d1939a4f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroup.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroup.spec.ts
@@ -8,6 +8,7 @@ import { makeTestGroup } from '../../../common/framework/test_group.js';
import { assert, makeValueTestVariant, unreachable } from '../../../common/util/util.js';
import {
allBindingEntries,
+ BindableResource,
bindingTypeInfo,
bufferBindingEntries,
bufferBindingTypeInfo,
@@ -106,7 +107,7 @@ g.test('binding_must_contain_resource_defined_in_layout')
.desc(
'Test that only compatible resource types specified in the BindGroupLayout are allowed for each entry.'
)
- .paramsSubcasesOnly(u =>
+ .params(u =>
u //
.combine('resourceType', kBindableResources)
.combine('entry', allBindingEntries(false))
@@ -121,6 +122,17 @@ g.test('binding_must_contain_resource_defined_in_layout')
const resource = t.getBindingResource(resourceType);
+ const IsStorageTextureResourceType = (resourceType: BindableResource) => {
+ switch (resourceType) {
+ case 'readonlyStorageTex':
+ case 'readwriteStorageTex':
+ case 'writeonlyStorageTex':
+ return true;
+ default:
+ return false;
+ }
+ };
+
let resourceBindingIsCompatible;
switch (info.resource) {
// Either type of sampler may be bound to a filtering sampler binding.
@@ -131,6 +143,11 @@ g.test('binding_must_contain_resource_defined_in_layout')
case 'nonFiltSamp':
resourceBindingIsCompatible = resourceType === 'nonFiltSamp';
break;
+ case 'readonlyStorageTex':
+ case 'readwriteStorageTex':
+ case 'writeonlyStorageTex':
+ resourceBindingIsCompatible = IsStorageTextureResourceType(resourceType);
+ break;
default:
resourceBindingIsCompatible = info.resource === resourceType;
break;
@@ -166,7 +183,7 @@ g.test('texture_binding_must_have_correct_usage')
const descriptor = {
size: { width: 16, height: 16, depthOrArrayLayers: 1 },
- format: 'rgba8unorm' as const,
+ format: 'r32float' as const,
usage: appliedUsage,
sampleCount: info.resource === 'sampledTexMS' ? 4 : 1,
};
@@ -313,6 +330,19 @@ g.test('texture_must_have_correct_dimension')
});
t.skipIfTextureViewDimensionNotSupported(viewDimension, dimension);
+ if (t.isCompatibility && texture.dimension === '2d') {
+ if (depthOrArrayLayers === 1) {
+ t.skipIf(
+ viewDimension !== '2d',
+ '1 layer 2d textures default to textureBindingViewDimension: "2d" in compat mode'
+ );
+ } else {
+ t.skipIf(
+ viewDimension !== '2d-array',
+ '> 1 layer 2d textures default to textureBindingViewDimension "2d-array" in compat mode'
+ );
+ }
+ }
const shouldError = viewDimension !== dimension;
const textureView = texture.createView({ dimension });
@@ -526,9 +556,7 @@ g.test('buffer,resource_state')
g.test('texture,resource_state')
.desc('Test bind group creation with various texture resource states')
.paramsSubcasesOnly(u =>
- u
- .combine('state', kResourceStates)
- .combine('entry', sampledAndStorageBindingEntries(true, 'rgba8unorm'))
+ u.combine('state', kResourceStates).combine('entry', sampledAndStorageBindingEntries(true))
)
.fn(t => {
const { state, entry } = t.params;
@@ -548,10 +576,11 @@ g.test('texture,resource_state')
const usage = entry.texture?.multisampled
? info.usage | GPUConst.TextureUsage.RENDER_ATTACHMENT
: info.usage;
+ const format = entry.storageTexture !== undefined ? 'r32float' : 'rgba8unorm';
const texture = t.createTextureWithState(state, {
usage,
size: [1, 1],
- format: 'rgba8unorm',
+ format,
sampleCount: entry.texture?.multisampled ? 4 : 1,
});
@@ -626,7 +655,9 @@ g.test('binding_resources,device_mismatch')
{ buffer: { type: 'storage' } },
{ sampler: { type: 'filtering' } },
{ texture: { multisampled: false } },
- { storageTexture: { access: 'write-only', format: 'rgba8unorm' } },
+ { storageTexture: { access: 'write-only', format: 'r32float' } },
+ { storageTexture: { access: 'read-only', format: 'r32float' } },
+ { storageTexture: { access: 'read-write', format: 'r32float' } },
] as const)
.beginSubcases()
.combineWithParams([
@@ -784,6 +815,10 @@ g.test('storage_texture,format')
.combine('storageTextureFormat', kStorageTextureFormats)
.combine('resourceFormat', kStorageTextureFormats)
)
+ .beforeAllSubcases(t => {
+ const { storageTextureFormat, resourceFormat } = t.params;
+ t.skipIfTextureFormatNotUsableAsStorageTexture(storageTextureFormat, resourceFormat);
+ })
.fn(t => {
const { storageTextureFormat, resourceFormat } = t.params;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroupLayout.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroupLayout.spec.ts
index a50247aa13..b09adc2af1 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroupLayout.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createBindGroupLayout.spec.ts
@@ -163,8 +163,10 @@ g.test('visibility,VERTEX_shader_stage_storage_texture_access')
.fn(t => {
const { shaderStage, access } = t.params;
+ const appliedAccess = access ?? 'write-only';
const success = !(
- (access ?? 'write-only') === 'write-only' && shaderStage & GPUShaderStage.VERTEX
+ // If visibility includes VERETX, storageTexture.access must be "read-only"
+ (shaderStage & GPUShaderStage.VERTEX && appliedAccess !== 'read-only')
);
t.expectValidationError(() => {
@@ -173,7 +175,7 @@ g.test('visibility,VERTEX_shader_stage_storage_texture_access')
{
binding: 0,
visibility: shaderStage,
- storageTexture: { access, format: 'rgba8unorm' },
+ storageTexture: { access, format: 'r32uint' },
},
],
});
@@ -436,29 +438,36 @@ g.test('storage_texture,layout_dimension')
g.test('storage_texture,formats')
.desc(
`
- Test that a validation error is generated if the format doesn't support the storage usage.
+ Test that a validation error is generated if the format doesn't support the storage usage. A
+ validation error is also generated if the format doesn't support the 'read-write' storage access
+ when the storage access is 'read-write'.
`
)
- .params(u => u.combine('format', kAllTextureFormats))
+ .params(u =>
+ u //
+ .combine('format', kAllTextureFormats) //
+ .combine('access', kStorageTextureAccessValues)
+ )
.beforeAllSubcases(t => {
t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
+ t.skipIfTextureFormatNotUsableAsStorageTexture(t.params.format);
})
.fn(t => {
- const { format } = t.params;
+ const { format, access } = t.params;
const info = kTextureFormatInfo[format];
- t.expectValidationError(
- () => {
- t.device.createBindGroupLayout({
- entries: [
- {
- binding: 0,
- visibility: GPUShaderStage.COMPUTE,
- storageTexture: { format },
- },
- ],
- });
- },
- !info.color?.storage
- );
+ const success =
+ info.color?.storage && !(access === 'read-write' && !info.color?.readWriteStorage);
+
+ t.expectValidationError(() => {
+ t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ storageTexture: { format, access },
+ },
+ ],
+ });
+ }, !success);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createTexture.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createTexture.spec.ts
index a9fe352b74..fc1c8b86b2 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createTexture.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createTexture.spec.ts
@@ -6,7 +6,7 @@ import { assert, makeValueTestVariant } from '../../../common/util/util.js';
import { kTextureDimensions, kTextureUsages } from '../../capability_info.js';
import { GPUConst } from '../../constants.js';
import {
- kTextureFormats,
+ kAllTextureFormats,
kTextureFormatInfo,
kCompressedTextureFormats,
kUncompressedTextureFormats,
@@ -15,6 +15,7 @@ import {
filterFormatsByFeature,
viewCompatible,
textureDimensionAndFormatCompatible,
+ isTextureFormatUsableAsStorageFormat,
} from '../../format_info.js';
import { maxMipLevelCount } from '../../util/texture/base.js';
@@ -103,7 +104,9 @@ g.test('dimension_type_and_format_compatibility')
`Test every dimension type on every format. Note that compressed formats and depth/stencil formats are not valid for 1D/3D dimension types.`
)
.params(u =>
- u.combine('dimension', [undefined, ...kTextureDimensions]).combine('format', kTextureFormats)
+ u //
+ .combine('dimension', [undefined, ...kTextureDimensions])
+ .combine('format', kAllTextureFormats)
)
.beforeAllSubcases(t => {
const { format } = t.params;
@@ -135,7 +138,7 @@ g.test('mipLevelCount,format')
.params(u =>
u
.combine('dimension', [undefined, ...kTextureDimensions])
- .combine('format', kTextureFormats)
+ .combine('format', kAllTextureFormats)
.beginSubcases()
.combine('mipLevelCount', [1, 2, 3, 6, 7])
// Filter out incompatible dimension type and format combinations.
@@ -270,7 +273,7 @@ g.test('sampleCount,various_sampleCount_with_all_formats')
.params(u =>
u
.combine('dimension', [undefined, '2d'] as const)
- .combine('format', kTextureFormats)
+ .combine('format', kAllTextureFormats)
.beginSubcases()
.combine('sampleCount', [0, 1, 2, 4, 8, 16, 32, 256])
)
@@ -296,7 +299,7 @@ g.test('sampleCount,various_sampleCount_with_all_formats')
usage,
};
- const success = sampleCount === 1 || (sampleCount === 4 && info.multisample && info.renderable);
+ const success = sampleCount === 1 || (sampleCount === 4 && info.multisample);
t.expectValidationError(() => {
t.device.createTexture(descriptor);
@@ -317,7 +320,7 @@ g.test('sampleCount,valid_sampleCount_with_other_parameter_varies')
.params(u =>
u
.combine('dimension', [undefined, ...kTextureDimensions])
- .combine('format', kTextureFormats)
+ .combine('format', kAllTextureFormats)
.beginSubcases()
.combine('sampleCount', [1, 4])
.combine('arrayLayerCount', [1, 2])
@@ -372,8 +375,12 @@ g.test('sampleCount,valid_sampleCount_with_other_parameter_varies')
usage,
};
+ const satisfyWithStorageUsageRequirement =
+ (usage & GPUConst.TextureUsage.STORAGE_BINDING) === 0 ||
+ isTextureFormatUsableAsStorageFormat(format, t.isCompatibility);
+
const success =
- sampleCount === 1 ||
+ (sampleCount === 1 && satisfyWithStorageUsageRequirement) ||
(sampleCount === 4 &&
(dimension === '2d' || dimension === undefined) &&
kTextureFormatInfo[format].multisample &&
@@ -589,6 +596,7 @@ g.test('texture_size,2d_texture,compressed_format')
u
.combine('dimension', [undefined, '2d'] as const)
.combine('format', kCompressedTextureFormats)
+ .beginSubcases()
.expand('sizeVariant', p => {
const { blockWidth, blockHeight } = kTextureFormatInfo[p.format];
return [
@@ -1026,7 +1034,7 @@ g.test('texture_usage')
.params(u =>
u
.combine('dimension', [undefined, ...kTextureDimensions])
- .combine('format', kTextureFormats)
+ .combine('format', kAllTextureFormats)
.beginSubcases()
// If usage0 and usage1 are the same, then the usage being test is a single usage. Otherwise, it is a combined usage.
.combine('usage0', kTextureUsages)
@@ -1058,12 +1066,13 @@ g.test('texture_usage')
// Note that we unconditionally test copy usages for all formats. We don't check copySrc/copyDst in kTextureFormatInfo in capability_info.js
// if (!info.copySrc && (usage & GPUTextureUsage.COPY_SRC) !== 0) success = false;
// if (!info.copyDst && (usage & GPUTextureUsage.COPY_DST) !== 0) success = false;
- if (!info.color?.storage && (usage & GPUTextureUsage.STORAGE_BINDING) !== 0) success = false;
- if (
- (!info.renderable || appliedDimension !== '2d') &&
- (usage & GPUTextureUsage.RENDER_ATTACHMENT) !== 0
- )
- success = false;
+ if (usage & GPUTextureUsage.STORAGE_BINDING) {
+ if (!isTextureFormatUsableAsStorageFormat(format, t.isCompatibility)) success = false;
+ }
+ if (usage & GPUTextureUsage.RENDER_ATTACHMENT) {
+ if (appliedDimension === '1d') success = false;
+ if (info.color && !info.colorRender) success = false;
+ }
t.expectValidationError(() => {
t.device.createTexture(descriptor);
@@ -1080,10 +1089,10 @@ g.test('viewFormats')
.combine('viewFormatFeature', kFeaturesForFormats)
.beginSubcases()
.expand('format', ({ formatFeature }) =>
- filterFormatsByFeature(formatFeature, kTextureFormats)
+ filterFormatsByFeature(formatFeature, kAllTextureFormats)
)
.expand('viewFormat', ({ viewFormatFeature }) =>
- filterFormatsByFeature(viewFormatFeature, kTextureFormats)
+ filterFormatsByFeature(viewFormatFeature, kAllTextureFormats)
)
)
.beforeAllSubcases(t => {
@@ -1096,7 +1105,7 @@ g.test('viewFormats')
t.skipIfTextureFormatNotSupported(format, viewFormat);
- const compatible = viewCompatible(format, viewFormat);
+ const compatible = viewCompatible(t.isCompatibility, format, viewFormat);
// Test the viewFormat in the list.
t.expectValidationError(() => {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createView.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createView.spec.ts
index e4871c5d80..8949ea1eeb 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createView.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/createView.spec.ts
@@ -10,7 +10,7 @@ import {
} from '../../capability_info.js';
import {
kTextureFormatInfo,
- kTextureFormats,
+ kAllTextureFormats,
kFeaturesForFormats,
filterFormatsByFeature,
viewCompatible,
@@ -39,10 +39,10 @@ g.test('format')
.combine('viewFormatFeature', kFeaturesForFormats)
.beginSubcases()
.expand('textureFormat', ({ textureFormatFeature }) =>
- filterFormatsByFeature(textureFormatFeature, kTextureFormats)
+ filterFormatsByFeature(textureFormatFeature, kAllTextureFormats)
)
.expand('viewFormat', ({ viewFormatFeature }) =>
- filterFormatsByFeature(viewFormatFeature, [undefined, ...kTextureFormats])
+ filterFormatsByFeature(viewFormatFeature, [undefined, ...kAllTextureFormats])
)
.combine('useViewFormatList', [false, true])
)
@@ -56,7 +56,8 @@ g.test('format')
t.skipIfTextureFormatNotSupported(textureFormat, viewFormat);
- const compatible = viewFormat === undefined || viewCompatible(textureFormat, viewFormat);
+ const compatible =
+ viewFormat === undefined || viewCompatible(t.isCompatibility, textureFormat, viewFormat);
const texture = t.device.createTexture({
format: textureFormat,
@@ -123,7 +124,7 @@ g.test('aspect')
)
.params(u =>
u //
- .combine('format', kTextureFormats)
+ .combine('format', kAllTextureFormats)
.combine('aspect', kTextureAspects)
)
.beforeAllSubcases(t => {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts
index f1c3d91e29..75f4a092fc 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts
@@ -6,7 +6,7 @@ import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { kTextureUsages, kTextureDimensions } from '../../../../capability_info.js';
import {
kTextureFormatInfo,
- kTextureFormats,
+ kAllTextureFormats,
kCompressedTextureFormats,
kDepthStencilFormats,
kFeaturesForFormats,
@@ -255,6 +255,12 @@ Test that textures in copyTextureToTexture must have the same sample count.
.combine('srcSampleCount', [1, 4])
.combine('dstSampleCount', [1, 4])
)
+ .beforeAllSubcases(t => {
+ t.skipIf(
+ t.isCompatibility && (t.params.srcSampleCount !== 1 || t.params.dstSampleCount !== 1),
+ 'multisample textures are not copyable in compatibility mode'
+ );
+ })
.fn(t => {
const { srcSampleCount, dstSampleCount } = t.params;
@@ -307,6 +313,9 @@ TODO: Check the source and destination constraints separately.
.expand('copyWidth', p => [32 - Math.max(p.srcCopyOrigin.x, p.dstCopyOrigin.x), 16])
.expand('copyHeight', p => [16 - Math.max(p.srcCopyOrigin.y, p.dstCopyOrigin.y), 8])
)
+ .beforeAllSubcases(t => {
+ t.skipIf(t.isCompatibility, 'multisample textures are not copyable in compatibility mode');
+ })
.fn(t => {
const { srcCopyOrigin, dstCopyOrigin, copyWidth, copyHeight } = t.params;
@@ -351,10 +360,10 @@ Test the formats of textures in copyTextureToTexture must be copy-compatible.
.combine('dstFormatFeature', kFeaturesForFormats)
.beginSubcases()
.expand('srcFormat', ({ srcFormatFeature }) =>
- filterFormatsByFeature(srcFormatFeature, kTextureFormats)
+ filterFormatsByFeature(srcFormatFeature, kAllTextureFormats)
)
.expand('dstFormat', ({ dstFormatFeature }) =>
- filterFormatsByFeature(dstFormatFeature, kTextureFormats)
+ filterFormatsByFeature(dstFormatFeature, kAllTextureFormats)
)
)
.beforeAllSubcases(t => {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/createRenderBundleEncoder.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/createRenderBundleEncoder.spec.ts
index 2eaa9b43fd..b4beeb8fbe 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/createRenderBundleEncoder.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/createRenderBundleEncoder.spec.ts
@@ -196,10 +196,7 @@ g.test('valid_texture_formats')
g.test('depth_stencil_readonly')
.desc(
`
- Tests that createRenderBundleEncoder validation of depthReadOnly and stencilReadOnly
- - With depth-only formats
- - With stencil-only formats
- - With depth-stencil-combined formats
+ Test that allow combinations of depth-stencil format, depthReadOnly and stencilReadOnly are allowed.
`
)
.params(u =>
@@ -215,44 +212,9 @@ g.test('depth_stencil_readonly')
})
.fn(t => {
const { depthStencilFormat, depthReadOnly, stencilReadOnly } = t.params;
-
- let shouldError = false;
- if (
- kTextureFormatInfo[depthStencilFormat].depth &&
- kTextureFormatInfo[depthStencilFormat].stencil &&
- depthReadOnly !== stencilReadOnly
- ) {
- shouldError = true;
- }
-
- t.expectValidationError(() => {
- t.device.createRenderBundleEncoder({
- colorFormats: [],
- depthStencilFormat,
- depthReadOnly,
- stencilReadOnly,
- });
- }, shouldError);
- });
-
-g.test('depth_stencil_readonly_with_undefined_depth')
- .desc(
- `
- Tests that createRenderBundleEncoder validation of depthReadOnly and stencilReadOnly is ignored
- if there is no depthStencilFormat set.
- `
- )
- .params(u =>
- u //
- .beginSubcases()
- .combine('depthReadOnly', [false, true])
- .combine('stencilReadOnly', [false, true])
- )
- .fn(t => {
- const { depthReadOnly, stencilReadOnly } = t.params;
-
t.device.createRenderBundleEncoder({
- colorFormats: ['bgra8unorm'],
+ colorFormats: [],
+ depthStencilFormat,
depthReadOnly,
stencilReadOnly,
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/encoder_open_state.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/encoder_open_state.spec.ts
index 0d56222eed..815ca5a198 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/encoder_open_state.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/encoder_open_state.spec.ts
@@ -57,7 +57,10 @@ class F extends ValidationTest {
export const g = makeTestGroup(F);
-type EncoderCommands = keyof Omit<GPUCommandEncoder, '__brand' | 'label' | 'finish'>;
+// MAINTENANCE_TODO: Remove writeTimestamp from here once it's (hopefully) added back to the spec.
+type EncoderCommands =
+ | keyof Omit<GPUCommandEncoder, '__brand' | 'label' | 'finish'>
+ | 'writeTimestamp';
const kEncoderCommandInfo: {
readonly [k in EncoderCommands]: {};
} = {
@@ -146,6 +149,8 @@ g.test('non_pass_commands')
`
Test that functions of GPUCommandEncoder generate a validation error if the encoder is already
finished.
+
+ TODO: writeTimestamp is removed from the spec so it's skipped if it TypeErrors.
`
)
.params(u =>
@@ -260,8 +265,11 @@ g.test('non_pass_commands')
}
break;
case 'writeTimestamp':
- {
- encoder.writeTimestamp(querySet, 0);
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (encoder as any).writeTimestamp(querySet, 0);
+ } catch (ex) {
+ t.skipIf(ex instanceof TypeError, 'writeTimestamp is actually not available');
}
break;
case 'resolveQuerySet':
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts
index 163c20c311..8da3be33de 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts
@@ -32,13 +32,32 @@ type ComputeCmd = (typeof kComputeCmds)[number];
const kRenderCmds = ['draw', 'drawIndexed', 'drawIndirect', 'drawIndexedIndirect'] as const;
type RenderCmd = (typeof kRenderCmds)[number];
+const kPipelineTypes = ['auto0', 'explicit'] as const;
+type PipelineType = (typeof kPipelineTypes)[number];
+const kBindingTypes = ['auto0', 'auto1', 'explicit'] as const;
+type BindingType = (typeof kBindingTypes)[number];
+
+const kEmptyBindGroup0Ndx = 0;
+const kEmptyBindGroup1Ndx = 1;
+const kNonEmptyBindGroup0Ndx = 2;
+const kNonEmptyBindGroup1Ndx = 3;
+
+// Swaps 2 array elements in place.
+function swapArrayElements<T>(array: T[], ndx1: number, ndx2: number) {
+ const t = array[ndx1];
+ array[ndx1] = array[ndx2];
+ array[ndx2] = t;
+}
+
// Test resource type compatibility in pipeline and bind group
// [1]: Need to add externalTexture
const kResourceTypes: ValidBindableResource[] = [
'uniformBuf',
'filtSamp',
'sampledTex',
- 'storageTex',
+ 'readonlyStorageTex',
+ 'writeonlyStorageTex',
+ 'readwriteStorageTex',
];
function getTestCmds(
@@ -75,7 +94,17 @@ class F extends ValidationTest {
if (entry.buffer !== undefined) return 'uniformBuf';
if (entry.sampler !== undefined) return 'filtSamp';
if (entry.texture !== undefined) return 'sampledTex';
- if (entry.storageTexture !== undefined) return 'storageTex';
+ if (entry.storageTexture !== undefined) {
+ switch (entry.storageTexture.access) {
+ case undefined:
+ case 'write-only':
+ return 'writeonlyStorageTex';
+ case 'read-only':
+ return 'readonlyStorageTex';
+ case 'read-write':
+ return 'readwriteStorageTex';
+ }
+ }
unreachable();
}
@@ -208,8 +237,14 @@ class F extends ValidationTest {
case 'sampledTex':
entry.texture = {}; // default sampleType: float
break;
- case 'storageTex':
- entry.storageTexture = { access: 'write-only', format: 'rgba8unorm' };
+ case 'readonlyStorageTex':
+ entry.storageTexture = { access: 'read-only', format: 'r32float' };
+ break;
+ case 'writeonlyStorageTex':
+ entry.storageTexture = { access: 'write-only', format: 'r32float' };
+ break;
+ case 'readwriteStorageTex':
+ entry.storageTexture = { access: 'read-write', format: 'r32float' };
break;
}
@@ -259,6 +294,135 @@ class F extends ValidationTest {
validateFinish(success);
}
+
+ runDefaultLayoutBindingTest<T extends GPURenderPipeline | GPUComputePipeline>({
+ visibility,
+ empty,
+ pipelineType,
+ bindingType,
+ swap,
+ success,
+ makePipelinesFn,
+ doCommandFn,
+ }: {
+ visibility: number;
+ empty: boolean;
+ pipelineType: PipelineType;
+ bindingType: BindingType;
+ swap: boolean;
+ success: boolean;
+ makePipelinesFn: (t: F, explicitPipelineLayout: GPUPipelineLayout) => T[];
+ doCommandFn: (params: {
+ t: F;
+ encoder: GPUCommandEncoder;
+ pipeline: T;
+ emptyBindGroups: GPUBindGroup[];
+ nonEmptyBindGroups: GPUBindGroup[];
+ }) => void;
+ }) {
+ const { device } = this;
+ const explicitEmptyBindGroupLayout = device.createBindGroupLayout({
+ entries: [],
+ });
+ const explicitBindGroupLayout = device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility,
+ buffer: {},
+ },
+ ],
+ });
+ const explicitPipelineLayout = device.createPipelineLayout({
+ bindGroupLayouts: [
+ explicitEmptyBindGroupLayout,
+ explicitEmptyBindGroupLayout,
+ explicitBindGroupLayout,
+ explicitBindGroupLayout,
+ ],
+ });
+
+ const [pipelineAuto0, pipelineAuto1, pipelineExplicit] = makePipelinesFn(
+ this,
+ explicitPipelineLayout
+ );
+
+ const buffer = device.createBuffer({
+ size: 16,
+ usage: GPUBufferUsage.UNIFORM,
+ });
+ this.trackForCleanup(buffer);
+
+ let emptyBindGroupLayouts;
+ let nonEmptyBindGroupLayouts;
+ const pipeline = pipelineType === 'auto0' ? pipelineAuto0 : pipelineExplicit;
+
+ // Gets a bindGroupLayout either from the explicit layout passed in
+ // or one of the 2 `layout: 'auto'` pipelines.
+ const getBindGroupLayout = (
+ explicitBindGroupLayout: GPUBindGroupLayout,
+ bindGroupIndex: number
+ ) =>
+ bindingType === 'explicit'
+ ? explicitBindGroupLayout
+ : bindingType === 'auto0'
+ ? pipelineAuto0.getBindGroupLayout(bindGroupIndex)
+ : pipelineAuto1.getBindGroupLayout(bindGroupIndex);
+
+ if (empty) {
+ // Testing empty:
+ // - emptyBindGroupLayout comes from a possibly incompatible place.
+ // - nonEmptyBindGroupLayout comes from the pipeline we'll render/compute with.
+ emptyBindGroupLayouts = [
+ getBindGroupLayout(explicitEmptyBindGroupLayout, kEmptyBindGroup0Ndx),
+ getBindGroupLayout(explicitEmptyBindGroupLayout, kEmptyBindGroup1Ndx),
+ ];
+ if (swap) {
+ swapArrayElements(emptyBindGroupLayouts, 0, 1);
+ }
+ nonEmptyBindGroupLayouts = [
+ pipeline.getBindGroupLayout(kNonEmptyBindGroup0Ndx),
+ pipeline.getBindGroupLayout(kNonEmptyBindGroup1Ndx),
+ ];
+ } else {
+ // Testing non-empty:
+ // - nonEmptyBindGroupLayout comes from a possibly incompatible place.
+ // - emptyBindGroupLayout comes from the pipeline we'll render/compute with.
+ nonEmptyBindGroupLayouts = [
+ getBindGroupLayout(explicitBindGroupLayout, kNonEmptyBindGroup0Ndx),
+ getBindGroupLayout(explicitBindGroupLayout, kNonEmptyBindGroup1Ndx),
+ ];
+ if (swap) {
+ swapArrayElements(nonEmptyBindGroupLayouts, 0, 1);
+ }
+ emptyBindGroupLayouts = [
+ pipeline.getBindGroupLayout(kEmptyBindGroup0Ndx),
+ pipeline.getBindGroupLayout(kEmptyBindGroup1Ndx),
+ ];
+ }
+
+ const emptyBindGroups = emptyBindGroupLayouts.map(layout =>
+ device.createBindGroup({
+ layout,
+ entries: [],
+ })
+ );
+
+ const nonEmptyBindGroups = nonEmptyBindGroupLayouts.map(layout =>
+ device.createBindGroup({
+ layout,
+ entries: [{ binding: 0, resource: { buffer } }],
+ })
+ );
+
+ const encoder = device.createCommandEncoder();
+
+ doCommandFn({ t: this, encoder, pipeline, emptyBindGroups, nonEmptyBindGroups });
+
+ this.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+ }
}
export const g = makeTestGroup(F);
@@ -775,3 +939,174 @@ g.test('empty_bind_group_layouts_requires_empty_bind_groups,render_pass')
encoder.finish();
}, !success);
});
+
+// pipelineType specifies which pipeline to try to render/compute with
+// auto0 = the first `layout: 'auto'` pipeline
+// explicit = a pipeline crated with an explicit pipeline layout using explicit bind group layouts
+//
+// bindingType specifies where to get bindGroupLayouts to use to create bindGroups
+// auto0 = the first `layout: 'auto'` pipeline
+// auto1 = the second `layout: 'auto'` pipeline
+// explicit = a pipeline crated with an explicit pipeline layout using explicit bind group layouts
+//
+// swap specifies to swap the bindgroups we're testing. We test 2 of each type, 2 empty bindgroups and
+// 2 non-empty bindgroups. The 2 empty bindgroups, when swapped should still be compatible. Similarly
+// the 2 non-empty bindgroups, when swapped, should still be compatible.
+const kPipelineTypesAndBindingTypeParams = [
+ { pipelineType: 'auto0', bindingType: 'auto0', swap: false, _success: true },
+ { pipelineType: 'explicit', bindingType: 'explicit', swap: false, _success: true },
+ { pipelineType: 'explicit', bindingType: 'auto0', swap: false, _success: false },
+ { pipelineType: 'auto0', bindingType: 'explicit', swap: false, _success: false },
+ { pipelineType: 'auto0', bindingType: 'auto1', swap: false, _success: false },
+ { pipelineType: 'auto0', bindingType: 'auto0', swap: true, _success: true },
+] as const;
+
+g.test('default_bind_group_layouts_never_match,compute_pass')
+ .desc(
+ `
+ Test that bind groups created with default bind group layouts never match other layouts, including empty bind groups.
+
+ * Test that a pipeline with an explicit layout can not be used with a bindGroup from an auto layout
+ * Test that a pipeline with an auto layout can not be used with a bindGroup from an explicit layout
+ * Test that an auto layout from one pipeline can not be used with an auto layout from a different pipeline.
+ * Test matching bindgroup layouts on the same default layout pipeline are compatible. In other words if
+ you only define group(2) then group(0)'s empty layout and group(1)'s empty layout should be compatible.
+ Similarly if group(2) and group(3) have the same types of resources they should be compatible.
+ `
+ )
+ .params(u =>
+ u
+ .combineWithParams(kPipelineTypesAndBindingTypeParams)
+ .combine('empty', [false, true])
+ .combine('computeCommand', ['dispatchIndirect', 'dispatch'] as const)
+ )
+ .fn(t => {
+ const { pipelineType, bindingType, swap, _success: success, computeCommand, empty } = t.params;
+
+ t.runDefaultLayoutBindingTest<GPUComputePipeline>({
+ visibility: GPUShaderStage.COMPUTE,
+ empty,
+ pipelineType,
+ bindingType,
+ swap,
+ success,
+ makePipelinesFn: (t, explicitPipelineLayout) => {
+ return (['auto', 'auto', explicitPipelineLayout] as const).map<GPUComputePipeline>(layout =>
+ t.device.createComputePipeline({
+ layout,
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+ @group(2) @binding(0) var<uniform> u1: vec4f;
+ @group(3) @binding(0) var<uniform> u2: vec4f;
+ @compute @workgroup_size(2) fn main() { _ = u1; _ = u2; }
+ `,
+ }),
+ entryPoint: 'main',
+ },
+ })
+ );
+ },
+ doCommandFn: ({ t, encoder, pipeline, emptyBindGroups, nonEmptyBindGroups }) => {
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(kEmptyBindGroup0Ndx, emptyBindGroups[0]);
+ pass.setBindGroup(kEmptyBindGroup1Ndx, emptyBindGroups[1]);
+ pass.setBindGroup(kNonEmptyBindGroup0Ndx, nonEmptyBindGroups[0]);
+ pass.setBindGroup(kNonEmptyBindGroup1Ndx, nonEmptyBindGroups[1]);
+ t.doCompute(pass, computeCommand, true);
+ pass.end();
+ },
+ });
+ });
+
+g.test('default_bind_group_layouts_never_match,render_pass')
+ .desc(
+ `
+ Test that bind groups created with default bind group layouts never match other layouts, including empty bind groups.
+
+ * Test that a pipeline with an explicit layout can not be used with a bindGroup from an auto layout
+ * Test that a pipeline with an auto layout can not be used with a bindGroup from an explicit layout
+ * Test that an auto layout from one pipeline can not be used with an auto layout from a different pipeline.
+ * Test matching bindgroup layouts on the same default layout pipeline are compatible. In other words if
+ you only define group(2) then group(0)'s empty layout and group(1)'s empty layout should be compatible.
+ Similarly if group(2) and group(3) have the same types of resources they should be compatible.
+ `
+ )
+ .params(u =>
+ u
+ .combineWithParams(kPipelineTypesAndBindingTypeParams)
+ .combine('empty', [false, true])
+ .combine('renderCommand', [
+ 'draw',
+ 'drawIndexed',
+ 'drawIndirect',
+ 'drawIndexedIndirect',
+ ] as const)
+ )
+ .fn(t => {
+ const { pipelineType, bindingType, swap, _success: success, renderCommand, empty } = t.params;
+
+ t.runDefaultLayoutBindingTest<GPURenderPipeline>({
+ visibility: GPUShaderStage.VERTEX,
+ empty,
+ pipelineType,
+ bindingType,
+ swap,
+ success,
+ makePipelinesFn: (t, explicitPipelineLayout) => {
+ return (['auto', 'auto', explicitPipelineLayout] as const).map<GPURenderPipeline>(
+ layout => {
+ const colorFormat = 'rgba8unorm';
+ return t.device.createRenderPipeline({
+ layout,
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @group(2) @binding(0) var<uniform> u1: vec4f;
+ @group(3) @binding(0) var<uniform> u2: vec4f;
+ @vertex fn main() -> @builtin(position) vec4f { return u1 + u2; }
+ `,
+ }),
+ entryPoint: 'main',
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: `@fragment fn main() {}`,
+ }),
+ entryPoint: 'main',
+ targets: [{ format: colorFormat, writeMask: 0 }],
+ },
+ });
+ }
+ );
+ },
+ doCommandFn: ({ t, encoder, pipeline, emptyBindGroups, nonEmptyBindGroups }) => {
+ const attachmentTexture = t.device.createTexture({
+ format: 'rgba8unorm',
+ size: { width: 16, height: 16, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+ t.trackForCleanup(attachmentTexture);
+
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: attachmentTexture.createView(),
+ clearValue: [0, 0, 0, 0],
+ loadOp: 'clear',
+ storeOp: 'store',
+ },
+ ],
+ });
+
+ renderPass.setPipeline(pipeline);
+ renderPass.setBindGroup(kEmptyBindGroup0Ndx, emptyBindGroups[0]);
+ renderPass.setBindGroup(kEmptyBindGroup1Ndx, emptyBindGroups[1]);
+ renderPass.setBindGroup(kNonEmptyBindGroup0Ndx, nonEmptyBindGroups[0]);
+ renderPass.setBindGroup(kNonEmptyBindGroup1Ndx, nonEmptyBindGroups[1]);
+ t.doRender(renderPass, renderCommand, true);
+ renderPass.end();
+ },
+ });
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/queries/general.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/queries/general.spec.ts
index 0ed2352bfd..8c9c4bb551 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/queries/general.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/queries/general.spec.ts
@@ -68,14 +68,16 @@ Tests that begin occlusion query with query index:
encoder.validateFinish(t.params.queryIndex < 2);
});
-g.test('timestamp_query,query_type_and_index')
+g.test('writeTimestamp,query_type_and_index')
.desc(
`
Tests that write timestamp to all types of query set on all possible encoders:
- type {occlusion, timestamp}
- queryIndex {in, out of} range for GPUQuerySet
- x= {non-pass} encoder
- `
+
+TODO: writeTimestamp is removed from the spec so it's skipped if it TypeErrors.
+`
)
.params(u =>
u
@@ -101,16 +103,23 @@ Tests that write timestamp to all types of query set on all possible encoders:
const querySet = createQuerySetWithType(t, type, count);
const encoder = t.createEncoder('non-pass');
- encoder.encoder.writeTimestamp(querySet, queryIndex);
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (encoder.encoder as any).writeTimestamp(querySet, queryIndex);
+ } catch (ex) {
+ t.skipIf(ex instanceof TypeError, 'writeTimestamp is actually not available');
+ }
encoder.validateFinish(type === 'timestamp' && queryIndex < count);
});
-g.test('timestamp_query,invalid_query_set')
+g.test('writeTimestamp,invalid_query_set')
.desc(
`
Tests that write timestamp to a invalid query set that failed during creation:
- x= {non-pass} encoder
- `
+
+TODO: writeTimestamp is removed from the spec so it's skipped if it TypeErrors.
+`
)
.paramsSubcasesOnly(u => u.combine('querySetState', ['valid', 'invalid'] as const))
.beforeAllSubcases(t => {
@@ -125,12 +134,22 @@ Tests that write timestamp to a invalid query set that failed during creation:
});
const encoder = t.createEncoder('non-pass');
- encoder.encoder.writeTimestamp(querySet, 0);
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (encoder.encoder as any).writeTimestamp(querySet, 0);
+ } catch (ex) {
+ t.skipIf(ex instanceof TypeError, 'writeTimestamp is actually not available');
+ }
encoder.validateFinish(querySetState !== 'invalid');
});
-g.test('timestamp_query,device_mismatch')
- .desc('Tests writeTimestamp cannot be called with a query set created from another device')
+g.test('writeTimestamp,device_mismatch')
+ .desc(
+ `Tests writeTimestamp cannot be called with a query set created from another device
+
+ TODO: writeTimestamp is removed from the spec so it's skipped if it TypeErrors.
+ `
+ )
.paramsSubcasesOnly(u => u.combine('mismatched', [true, false]))
.beforeAllSubcases(t => {
t.selectDeviceForQueryTypeOrSkipTestCase('timestamp');
@@ -147,6 +166,11 @@ g.test('timestamp_query,device_mismatch')
t.trackForCleanup(querySet);
const encoder = t.createEncoder('non-pass');
- encoder.encoder.writeTimestamp(querySet, 0);
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (encoder.encoder as any).writeTimestamp(querySet, 0);
+ } catch (ex) {
+ t.skipIf(ex instanceof TypeError, 'writeTimestamp is actually not available');
+ }
encoder.validateFinish(!mismatched);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/render_bundle.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/render_bundle.spec.ts
index 883b634446..35bdc99583 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/render_bundle.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/render_bundle.spec.ts
@@ -3,7 +3,7 @@ Tests execution of render bundles.
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
-import { kDepthStencilFormats, kTextureFormatInfo } from '../../../format_info.js';
+import { kDepthStencilFormats } from '../../../format_info.js';
import { ValidationTest } from '../validation_test.js';
export const g = makeTestGroup(ValidationTest);
@@ -170,18 +170,6 @@ g.test('depth_stencil_readonly_mismatch')
.combine('bundleStencilReadOnly', [false, true])
.combine('passDepthReadOnly', [false, true])
.combine('passStencilReadOnly', [false, true])
- .filter(p => {
- // For combined depth/stencil formats the depth and stencil read only state must match
- // in order to create a valid render bundle or render pass.
- const depthStencilInfo = kTextureFormatInfo[p.depthStencilFormat];
- if (depthStencilInfo.depth && depthStencilInfo.stencil) {
- return (
- p.passDepthReadOnly === p.passStencilReadOnly &&
- p.bundleDepthReadOnly === p.bundleStencilReadOnly
- );
- }
- return true;
- })
)
.beforeAllSubcases(t => {
t.selectDeviceForTextureFormatOrSkipTestCase(t.params.depthStencilFormat);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/error_scope.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/error_scope.spec.ts
index cb5581fed6..8a8369dc3b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/error_scope.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/error_scope.spec.ts
@@ -26,7 +26,17 @@ class ErrorScopeTests extends Fixture {
const gpu = getGPU(this.rec);
const adapter = await gpu.requestAdapter();
assert(adapter !== null);
- const device = await adapter.requestDevice();
+
+ // We need to max out the adapter limits related to texture dimensions to more reliably cause an
+ // OOM error when asked for it, so set that on the device now.
+ const device = this.trackForCleanup(
+ await adapter.requestDevice({
+ requiredLimits: {
+ maxTextureDimension2D: adapter.limits.maxTextureDimension2D,
+ maxTextureArrayLayers: adapter.limits.maxTextureArrayLayers,
+ },
+ })
+ );
assert(device !== null);
this._device = device;
}
@@ -146,7 +156,7 @@ Tests that popping an empty error scope stack should reject.
)
.fn(t => {
const promise = t.device.popErrorScope();
- t.shouldReject('OperationError', promise);
+ t.shouldReject('OperationError', promise, { allowMissingStack: true });
});
g.test('parent_scope')
@@ -250,7 +260,7 @@ Tests that sibling error scopes need to be balanced.
{
// Trying to pop an additional non-existing scope should reject.
const promise = t.device.popErrorScope();
- t.shouldReject('OperationError', promise);
+ t.shouldReject('OperationError', promise, { allowMissingStack: true });
}
const errors = await Promise.all(promises);
@@ -286,6 +296,6 @@ Tests that nested error scopes need to be balanced.
{
// Trying to pop an additional non-existing scope should reject.
const promise = t.device.popErrorScope();
- t.shouldReject('OperationError', promise);
+ t.shouldReject('OperationError', promise, { allowMissingStack: true });
}
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/gpu_external_texture_expiration.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/gpu_external_texture_expiration.spec.ts
index 7d77329920..20ea4897e6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/gpu_external_texture_expiration.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/gpu_external_texture_expiration.spec.ts
@@ -86,10 +86,7 @@ g.test('import_multiple_times_in_same_task_scope')
sourceType === 'VideoFrame'
? await getVideoFrameFromVideoElement(t, videoElement)
: videoElement;
- externalTexture = t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
- });
+ externalTexture = t.device.importExternalTexture({ source });
bindGroup = t.device.createBindGroup({
layout: t.getDefaultBindGroupLayout(),
@@ -99,10 +96,7 @@ g.test('import_multiple_times_in_same_task_scope')
t.submitCommandBuffer(bindGroup, true);
// Import again in the same task scope should return same object.
- const mayBeTheSameExternalTexture = t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
- });
+ const mayBeTheSameExternalTexture = t.device.importExternalTexture({ source });
if (externalTexture === mayBeTheSameExternalTexture) {
t.submitCommandBuffer(bindGroup, true);
@@ -142,10 +136,7 @@ g.test('import_and_use_in_different_microtask')
// Import GPUExternalTexture
queueMicrotask(() => {
- externalTexture = t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
- });
+ externalTexture = t.device.importExternalTexture({ source });
});
// Submit GPUExternalTexture
@@ -182,10 +173,7 @@ g.test('import_and_use_in_different_task')
sourceType === 'VideoFrame'
? await getVideoFrameFromVideoElement(t, videoElement)
: videoElement;
- externalTexture = t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
- });
+ externalTexture = t.device.importExternalTexture({ source });
bindGroup = t.device.createBindGroup({
layout: t.getDefaultBindGroupLayout(),
@@ -218,10 +206,7 @@ g.test('use_import_to_refresh')
let source: HTMLVideoElement | VideoFrame;
await startPlayingAndWaitForVideo(videoElement, () => {
source = videoElement;
- externalTexture = t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
- });
+ externalTexture = t.device.importExternalTexture({ source });
bindGroup = t.device.createBindGroup({
layout: t.getDefaultBindGroupLayout(),
@@ -232,10 +217,7 @@ g.test('use_import_to_refresh')
});
await waitForNextTask(() => {
- const mayBeTheSameExternalTexture = t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
- });
+ const mayBeTheSameExternalTexture = t.device.importExternalTexture({ source });
if (externalTexture === mayBeTheSameExternalTexture) {
// ImportExternalTexture should refresh expired GPUExternalTexture.
@@ -264,10 +246,7 @@ g.test('webcodec_video_frame_close_expire_immediately')
let externalTexture: GPUExternalTexture;
await startPlayingAndWaitForVideo(videoElement, async () => {
const source = await getVideoFrameFromVideoElement(t, videoElement);
- externalTexture = t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
- });
+ externalTexture = t.device.importExternalTexture({ source });
bindGroup = t.device.createBindGroup({
layout: t.getDefaultBindGroupLayout(),
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/image_copy.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/image_copy.ts
index 686a5ee1cf..0290046675 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/image_copy.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/image_copy.ts
@@ -255,9 +255,9 @@ export function formatCopyableWithMethod({ format, method }: WithFormatAndMethod
return supportedAspects.length > 0;
}
if (method === 'CopyT2B') {
- return info.copySrc;
+ return info.color.copySrc;
} else {
- return info.copyDst;
+ return info.color.copyDst;
}
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/texture_related.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/texture_related.spec.ts
index a0fe38e8e3..cbc36b1431 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/texture_related.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/texture_related.spec.ts
@@ -440,7 +440,7 @@ Test that the copy size must be aligned to the texture's format's block size.
const texture = t.createAlignedTexture(format, size, origin, dimension);
const bytesPerRow = align(
- Math.max(1, Math.ceil(size.width / info.blockWidth)) * info.bytesPerBlock,
+ Math.max(1, Math.ceil(size.width / info.blockWidth)) * info.color.bytes,
256
);
const rowsPerImage = Math.ceil(size.height / info.blockHeight);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/layout_shader_compat.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/layout_shader_compat.spec.ts
index 986fc42296..2b5e609c55 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/layout_shader_compat.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/layout_shader_compat.spec.ts
@@ -1,14 +1,293 @@
export const description = `
TODO:
- interface matching between pipeline layout and shader
- - x= {compute, vertex, fragment, vertex+fragment}, visibilities
- x= bind group index values, binding index values, multiple bindings
- - x= types of bindings
- - x= {equal, superset, subset}
+ - x= {superset, subset}
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
+import {
+ kShaderStageCombinations,
+ kShaderStages,
+ ValidBindableResource,
+} from '../../capability_info.js';
+import { GPUConst } from '../../constants.js';
import { ValidationTest } from './validation_test.js';
-export const g = makeTestGroup(ValidationTest);
+type BindableResourceType = ValidBindableResource | 'readonlyStorageBuf';
+const kBindableResources = [
+ 'uniformBuf',
+ 'storageBuf',
+ 'readonlyStorageBuf',
+ 'filtSamp',
+ 'nonFiltSamp',
+ 'compareSamp',
+ 'sampledTex',
+ 'sampledTexMS',
+ 'readonlyStorageTex',
+ 'writeonlyStorageTex',
+ 'readwriteStorageTex',
+] as const;
+
+const bindGroupLayoutEntryContents = {
+ compareSamp: {
+ sampler: {
+ type: 'comparison',
+ },
+ },
+ filtSamp: {
+ sampler: {
+ type: 'filtering',
+ },
+ },
+ nonFiltSamp: {
+ sampler: {
+ type: 'non-filtering',
+ },
+ },
+ sampledTex: {
+ texture: {
+ sampleType: 'unfilterable-float',
+ },
+ },
+ sampledTexMS: {
+ texture: {
+ sampleType: 'unfilterable-float',
+ multisampled: true,
+ },
+ },
+ storageBuf: {
+ buffer: {
+ type: 'storage',
+ },
+ },
+ readonlyStorageBuf: {
+ buffer: {
+ type: 'read-only-storage',
+ },
+ },
+ uniformBuf: {
+ buffer: {
+ type: 'uniform',
+ },
+ },
+ readonlyStorageTex: {
+ storageTexture: {
+ format: 'r32float',
+ access: 'read-only',
+ },
+ },
+ writeonlyStorageTex: {
+ storageTexture: {
+ format: 'r32float',
+ access: 'write-only',
+ },
+ },
+ readwriteStorageTex: {
+ storageTexture: {
+ format: 'r32float',
+ access: 'read-write',
+ },
+ },
+} as const;
+
+class F extends ValidationTest {
+ createPipelineLayout(
+ bindingInPipelineLayout: BindableResourceType,
+ visibility: number
+ ): GPUPipelineLayout {
+ return this.device.createPipelineLayout({
+ bindGroupLayouts: [
+ this.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility,
+ ...bindGroupLayoutEntryContents[bindingInPipelineLayout],
+ },
+ ],
+ }),
+ ],
+ });
+ }
+
+ GetBindableResourceShaderDeclaration(bindableResource: BindableResourceType): string {
+ switch (bindableResource) {
+ case 'compareSamp':
+ return 'var tmp : sampler_comparison';
+ case 'filtSamp':
+ case 'nonFiltSamp':
+ return 'var tmp : sampler';
+ case 'sampledTex':
+ return 'var tmp : texture_2d<f32>';
+ case 'sampledTexMS':
+ return 'var tmp : texture_multisampled_2d<f32>';
+ case 'storageBuf':
+ return 'var<storage, read_write> tmp : vec4u';
+ case 'readonlyStorageBuf':
+ return 'var<storage, read> tmp : vec4u';
+ case 'uniformBuf':
+ return 'var<uniform> tmp : vec4u;';
+ case 'readonlyStorageTex':
+ return 'var tmp : texture_storage_2d<r32float, read>';
+ case 'writeonlyStorageTex':
+ return 'var tmp : texture_storage_2d<r32float, write>';
+ case 'readwriteStorageTex':
+ return 'var tmp : texture_storage_2d<r32float, read_write>';
+ }
+ }
+}
+
+const BindingResourceCompatibleWithShaderStages = function (
+ bindingResource: BindableResourceType,
+ shaderStages: number
+): boolean {
+ if ((shaderStages & GPUConst.ShaderStage.VERTEX) > 0) {
+ switch (bindingResource) {
+ case 'writeonlyStorageTex':
+ case 'readwriteStorageTex':
+ case 'storageBuf':
+ return false;
+ default:
+ break;
+ }
+ }
+ return true;
+};
+
+export const g = makeTestGroup(F);
+
+g.test('pipeline_layout_shader_exact_match')
+ .desc(
+ `
+ Test that the binding type in the pipeline layout must match the related declaration in shader.
+ Note that read-write storage textures in the pipeline layout can match write-only storage textures
+ in the shader.
+ `
+ )
+ .params(u =>
+ u
+ .combine('bindingInPipelineLayout', kBindableResources)
+ .combine('bindingInShader', kBindableResources)
+ .beginSubcases()
+ .combine('pipelineLayoutVisibility', kShaderStageCombinations)
+ .combine('shaderStageWithBinding', kShaderStages)
+ .combine('isBindingStaticallyUsed', [true, false] as const)
+ .unless(
+ p =>
+ // We don't test using non-filtering sampler in shader because it has the same declaration
+ // as filtering sampler.
+ p.bindingInShader === 'nonFiltSamp' ||
+ !BindingResourceCompatibleWithShaderStages(
+ p.bindingInPipelineLayout,
+ p.pipelineLayoutVisibility
+ ) ||
+ !BindingResourceCompatibleWithShaderStages(p.bindingInShader, p.shaderStageWithBinding)
+ )
+ )
+ .fn(t => {
+ const {
+ bindingInPipelineLayout,
+ bindingInShader,
+ pipelineLayoutVisibility,
+ shaderStageWithBinding,
+ isBindingStaticallyUsed,
+ } = t.params;
+
+ const layout = t.createPipelineLayout(bindingInPipelineLayout, pipelineLayoutVisibility);
+ const bindResourceDeclaration = `@group(0) @binding(0) ${t.GetBindableResourceShaderDeclaration(
+ bindingInShader
+ )}`;
+ const staticallyUseBinding = isBindingStaticallyUsed ? '_ = tmp; ' : '';
+ const isAsync = false;
+
+ let success = true;
+ if (isBindingStaticallyUsed) {
+ success = bindingInPipelineLayout === bindingInShader;
+
+ // Filtering and non-filtering both have the same shader declaration.
+ success ||= bindingInPipelineLayout === 'nonFiltSamp' && bindingInShader === 'filtSamp';
+
+ // Promoting storage textures that are read-write in the layout can be readonly in the shader.
+ success ||=
+ bindingInPipelineLayout === 'readwriteStorageTex' &&
+ bindingInShader === 'writeonlyStorageTex';
+
+ // The shader using the resource must be included in the visibility in the layout.
+ success &&= (pipelineLayoutVisibility & shaderStageWithBinding) > 0;
+ }
+
+ switch (shaderStageWithBinding) {
+ case GPUConst.ShaderStage.COMPUTE: {
+ const computeShader = `
+ ${bindResourceDeclaration};
+ @compute @workgroup_size(1)
+ fn main() {
+ ${staticallyUseBinding}
+ }
+ `;
+ t.doCreateComputePipelineTest(isAsync, success, {
+ layout,
+ compute: {
+ module: t.device.createShaderModule({
+ code: computeShader,
+ }),
+ },
+ });
+ break;
+ }
+ case GPUConst.ShaderStage.VERTEX: {
+ const vertexShader = `
+ ${bindResourceDeclaration};
+ @vertex
+ fn main() -> @builtin(position) vec4f {
+ ${staticallyUseBinding}
+ return vec4f();
+ }
+ `;
+ t.doCreateRenderPipelineTest(isAsync, success, {
+ layout,
+ vertex: {
+ module: t.device.createShaderModule({
+ code: vertexShader,
+ }),
+ },
+ });
+ break;
+ }
+ case GPUConst.ShaderStage.FRAGMENT: {
+ const fragmentShader = `
+ ${bindResourceDeclaration};
+ @fragment
+ fn main() -> @location(0) vec4f {
+ ${staticallyUseBinding}
+ return vec4f();
+ }
+ `;
+ t.doCreateRenderPipelineTest(isAsync, success, {
+ layout,
+ vertex: {
+ module: t.device.createShaderModule({
+ code: `
+ @vertex
+ fn main() -> @builtin(position) vec4f {
+ return vec4f();
+ }`,
+ }),
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: fragmentShader,
+ }),
+ targets: [
+ {
+ format: 'rgba8unorm',
+ },
+ ],
+ },
+ });
+ break;
+ }
+ }
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.ts
index 4ac240d66e..34d7ef1c83 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.ts
@@ -14,7 +14,7 @@ import { raceWithRejectOnTimeout, unreachable, assert } from '../../../../../com
import { kTextureUsages } from '../../../../capability_info.js';
import {
kTextureFormatInfo,
- kTextureFormats,
+ kAllTextureFormats,
kValidTextureFormatsForCopyE2T,
} from '../../../../format_info.js';
import { kResourceStates } from '../../../../gpu_test.js';
@@ -669,7 +669,7 @@ g.test('destination_texture,format')
)
.params(u =>
u
- .combine('format', kTextureFormats)
+ .combine('format', kAllTextureFormats)
.beginSubcases()
.combine('copySize', [
{ width: 0, height: 0, depthOrArrayLayers: 0 },
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/query_set.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/query_set.spec.ts
index 1d8adab7e8..987a88830d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/query_set.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/query_set.spec.ts
@@ -24,11 +24,13 @@ Tests that use a destroyed query set in occlusion query on render pass encoder.
encoder.validateFinishAndSubmitGivenState(t.params.querySetState);
});
-g.test('writeTimestamp')
+g.test('timestamps')
.desc(
`
-Tests that use a destroyed query set in writeTimestamp on {non-pass, compute, render} encoder.
+Tests that use a destroyed query set in timestamp query on {non-pass, compute, render} encoder.
- x= {destroyed, not destroyed (control case)}
+
+ TODO: writeTimestamp is removed from the spec so it's skipped if it TypeErrors.
`
)
.params(u => u.beginSubcases().combine('querySetState', ['valid', 'destroyed'] as const))
@@ -39,9 +41,50 @@ Tests that use a destroyed query set in writeTimestamp on {non-pass, compute, re
count: 2,
});
- const encoder = t.createEncoder('non-pass');
- encoder.encoder.writeTimestamp(querySet, 0);
- encoder.validateFinishAndSubmitGivenState(t.params.querySetState);
+ {
+ const encoder = t.createEncoder('non-pass');
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (encoder.encoder as any).writeTimestamp(querySet, 0);
+ } catch (ex) {
+ t.skipIf(ex instanceof TypeError, 'writeTimestamp is actually not available');
+ }
+ encoder.validateFinishAndSubmitGivenState(t.params.querySetState);
+ }
+
+ {
+ const encoder = t.createEncoder('non-pass');
+ encoder.encoder
+ .beginComputePass({
+ timestampWrites: { querySet, beginningOfPassWriteIndex: 0 },
+ })
+ .end();
+ encoder.validateFinishAndSubmitGivenState(t.params.querySetState);
+ }
+
+ {
+ const texture = t.trackForCleanup(
+ t.device.createTexture({
+ size: [1, 1, 1],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ })
+ );
+ const encoder = t.createEncoder('non-pass');
+ encoder.encoder
+ .beginRenderPass({
+ colorAttachments: [
+ {
+ view: texture.createView(),
+ loadOp: 'load',
+ storeOp: 'store',
+ },
+ ],
+ timestampWrites: { querySet, beginningOfPassWriteIndex: 0 },
+ })
+ .end();
+ encoder.validateFinishAndSubmitGivenState(t.params.querySetState);
+ }
});
g.test('resolveQuerySet')
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/texture.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/texture.spec.ts
index 42036bd881..8e73cbf67b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/texture.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/queue/destroyed/texture.spec.ts
@@ -165,15 +165,16 @@ Tests that using a destroyed texture referenced by a bindGroup set with setBindG
u
.combine('destroyed', [false, true] as const)
.combine('encoderType', ['compute pass', 'render pass', 'render bundle'] as const)
+ .combine('bindingType', ['texture', 'storageTexture'] as const)
)
.fn(t => {
- const { destroyed, encoderType } = t.params;
+ const { destroyed, encoderType, bindingType } = t.params;
const { device } = t;
const texture = t.trackForCleanup(
t.device.createTexture({
size: [1, 1, 1],
format: 'rgba8unorm',
- usage: GPUTextureUsage.TEXTURE_BINDING,
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
})
);
@@ -182,7 +183,9 @@ Tests that using a destroyed texture referenced by a bindGroup set with setBindG
{
binding: 0,
visibility: GPUShaderStage.COMPUTE,
- texture: {},
+ [bindingType]: {
+ format: texture.format,
+ },
},
],
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/attachment_compatibility.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/attachment_compatibility.spec.ts
index c0ab23b91c..241d576e39 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/attachment_compatibility.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/attachment_compatibility.spec.ts
@@ -551,13 +551,6 @@ Test that the depth stencil read only state in render passes or bundles is compa
.filter(p => {
if (p.format) {
const depthStencilInfo = kTextureFormatInfo[p.format];
- // For combined depth/stencil formats the depth and stencil read only state must match
- // in order to create a valid render bundle or render pass.
- if (depthStencilInfo.depth && depthStencilInfo.stencil) {
- if (p.depthReadOnly !== p.stencilReadOnly) {
- return false;
- }
- }
// If the format has no depth aspect, the depthReadOnly, depthWriteEnabled of the pipeline must not be true
// in order to create a valid render pipeline.
if (!depthStencilInfo.depth && p.depthWriteEnabled) {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts
index 9713beea52..0b471c5f6d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts
@@ -20,6 +20,7 @@ class F extends ValidationTest {
createTexture(
options: {
format?: GPUTextureFormat;
+ dimension?: GPUTextureDimension;
width?: number;
height?: number;
arrayLayerCount?: number;
@@ -30,6 +31,7 @@ class F extends ValidationTest {
): GPUTexture {
const {
format = 'rgba8unorm',
+ dimension = '2d',
width = 16,
height = 16,
arrayLayerCount = 1,
@@ -41,6 +43,7 @@ class F extends ValidationTest {
return this.device.createTexture({
size: { width, height, depthOrArrayLayers: arrayLayerCount },
format,
+ dimension,
mipLevelCount,
sampleCount,
usage,
@@ -90,6 +93,7 @@ class F extends ValidationTest {
}
export const g = makeTestGroup(F);
+const kArrayLayerCount = 10;
g.test('attachments,one_color_attachment')
.desc(`Test that a render pass works with only one color attachment.`)
@@ -278,6 +282,184 @@ g.test('color_attachments,limits,maxColorAttachmentBytesPerSample,unaligned')
t.tryRenderPass(success, { colorAttachments });
});
+g.test('color_attachments,depthSlice,definedness')
+ .desc(
+ `
+ Test that depthSlice must be undefined for 2d color attachments and defined for 3d color attachments."
+ - The special value '0xFFFFFFFF' is not treated as 'undefined'.
+ `
+ )
+ .params(u =>
+ u
+ .combine('dimension', ['2d', '3d'] as GPUTextureDimension[])
+ .beginSubcases()
+ .combine('depthSlice', [undefined, 0, 0xffffffff])
+ )
+ .fn(t => {
+ const { dimension, depthSlice } = t.params;
+ const texture = t.createTexture({ dimension });
+
+ const colorAttachment = t.getColorAttachment(texture);
+ if (depthSlice !== undefined) {
+ colorAttachment.depthSlice = depthSlice;
+ }
+
+ const descriptor: GPURenderPassDescriptor = {
+ colorAttachments: [colorAttachment],
+ };
+
+ const success =
+ (dimension === '2d' && depthSlice === undefined) || (dimension === '3d' && depthSlice === 0);
+
+ t.tryRenderPass(success, descriptor);
+ });
+
+g.test('color_attachments,depthSlice,bound_check')
+ .desc(
+ `
+ Test that depthSlice must be less than the depthOrArrayLayers of 3d texture's subresource at mip levels.
+ - Check depth bounds with 3d texture size [16, 1, 10], which has 5 mip levels with depth [10, 5, 2, 1, 1]
+ for testing more mip level size computation.
+ - Failed if depthSlice >= the depth of each mip level.
+ `
+ )
+ .params(u =>
+ u
+ .combine('mipLevel', [0, 1, 2, 3, 4])
+ .beginSubcases()
+ .expand('depthSlice', ({ mipLevel }) => {
+ const depthAtMipLevel = Math.max(kArrayLayerCount >> mipLevel, 1);
+ // Use Set() to exclude duplicates when the depthAtMipLevel is 1 and 2
+ return [...new Set([0, 1, depthAtMipLevel - 1, depthAtMipLevel])];
+ })
+ )
+ .fn(t => {
+ const { mipLevel, depthSlice } = t.params;
+
+ const texture = t.createTexture({
+ dimension: '3d',
+ width: 16,
+ height: 1,
+ arrayLayerCount: kArrayLayerCount,
+ mipLevelCount: mipLevel + 1,
+ });
+
+ const viewDescriptor: GPUTextureViewDescriptor = {
+ baseMipLevel: mipLevel,
+ mipLevelCount: 1,
+ baseArrayLayer: 0,
+ arrayLayerCount: 1,
+ };
+
+ const colorAttachment = t.getColorAttachment(texture, viewDescriptor);
+ colorAttachment.depthSlice = depthSlice;
+
+ const passDescriptor: GPURenderPassDescriptor = {
+ colorAttachments: [colorAttachment],
+ };
+
+ const success = depthSlice < Math.max(kArrayLayerCount >> mipLevel, 1);
+
+ t.tryRenderPass(success, passDescriptor);
+ });
+
+g.test('color_attachments,depthSlice,overlaps,same_miplevel')
+ .desc(
+ `
+ Test that the depth slices of 3d color attachments have no overlaps for same texture in a render
+ pass.
+ - Succeed if the depth slices are different, or from different textures, or on different render
+ passes.
+ - Fail if same depth slice from same texture's same mip level is overwritten in a render pass.
+ `
+ )
+ .params(u =>
+ u
+ .combine('sameDepthSlice', [true, false])
+ .beginSubcases()
+ .combine('sameTexture', [true, false])
+ .combine('samePass', [true, false])
+ )
+ .fn(t => {
+ const { sameDepthSlice, sameTexture, samePass } = t.params;
+ const arrayLayerCount = 4;
+
+ const texDescriptor = {
+ dimension: '3d' as GPUTextureDimension,
+ arrayLayerCount,
+ };
+ const texture = t.createTexture(texDescriptor);
+
+ const colorAttachments = [];
+ for (let i = 0; i < arrayLayerCount; i++) {
+ const colorAttachment = t.getColorAttachment(
+ sameTexture ? texture : t.createTexture(texDescriptor)
+ );
+ colorAttachment.depthSlice = sameDepthSlice ? 0 : i;
+ colorAttachments.push(colorAttachment);
+ }
+
+ const encoder = t.createEncoder('non-pass');
+ if (samePass) {
+ const pass = encoder.encoder.beginRenderPass({ colorAttachments });
+ pass.end();
+ } else {
+ for (let i = 0; i < arrayLayerCount; i++) {
+ const pass = encoder.encoder.beginRenderPass({ colorAttachments: [colorAttachments[i]] });
+ pass.end();
+ }
+ }
+
+ const success = !sameDepthSlice || !sameTexture || !samePass;
+
+ encoder.validateFinish(success);
+ });
+
+g.test('color_attachments,depthSlice,overlaps,diff_miplevel')
+ .desc(
+ `
+ Test that the same depth slice from different mip levels of a 3d texture with size [1, 1, N] can
+ be set in a render pass's color attachments.
+ `
+ )
+ .params(u => u.combine('sameMipLevel', [true, false]))
+ .fn(t => {
+ const { sameMipLevel } = t.params;
+ const mipLevelCount = 4;
+
+ const texDescriptor = {
+ dimension: '3d' as GPUTextureDimension,
+ width: 1,
+ height: 1,
+ arrayLayerCount: 1 << mipLevelCount,
+ mipLevelCount,
+ };
+ const texture = t.createTexture(texDescriptor);
+
+ const viewDescriptor: GPUTextureViewDescriptor = {
+ baseMipLevel: 0,
+ mipLevelCount: 1,
+ baseArrayLayer: 0,
+ arrayLayerCount: 1,
+ };
+
+ const colorAttachments = [];
+ for (let i = 0; i < mipLevelCount; i++) {
+ if (!sameMipLevel) {
+ viewDescriptor.baseMipLevel = i;
+ }
+ const colorAttachment = t.getColorAttachment(texture, viewDescriptor);
+ colorAttachment.depthSlice = 0;
+ colorAttachments.push(colorAttachment);
+ }
+
+ const encoder = t.createEncoder('non-pass');
+ const pass = encoder.encoder.beginRenderPass({ colorAttachments });
+ pass.end();
+
+ encoder.validateFinish(!sameMipLevel);
+ });
+
g.test('attachments,same_size')
.desc(
`
@@ -909,10 +1091,8 @@ g.test('depth_stencil_attachment,loadOp_storeOp_match_depthReadOnly_stencilReadO
const hasDepth = info.depth;
const hasStencil = info.stencil;
- const goodAspectCombo =
- (hasDepth && hasStencil ? !depthReadOnly === !stencilReadOnly : true) &&
- (hasDepthSettings ? hasDepth : true) &&
- (hasStencilSettings ? hasStencil : true);
+ const goodAspectSettingsPresent =
+ (hasDepthSettings ? hasDepth : true) && (hasStencilSettings ? hasStencil : true);
const hasBothDepthOps = !!depthLoadOp && !!depthStoreOp;
const hasBothStencilOps = !!stencilLoadOp && !!stencilStoreOp;
@@ -923,7 +1103,7 @@ g.test('depth_stencil_attachment,loadOp_storeOp_match_depthReadOnly_stencilReadO
const goodStencilCombo =
hasStencil && !stencilReadOnly ? hasBothStencilOps : hasNeitherStencilOps;
- const shouldError = !goodAspectCombo || !goodDepthCombo || !goodStencilCombo;
+ const shouldError = !goodAspectSettingsPresent || !goodDepthCombo || !goodStencilCombo;
t.expectValidationError(() => {
encoder.finish();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/common.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/common.ts
index 93b0932042..e0316a5517 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/common.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/common.ts
@@ -1,4 +1,4 @@
-import { kTextureFormatInfo } from '../../../format_info.js';
+import { ColorTextureFormat, kTextureFormatInfo } from '../../../format_info.js';
import {
getFragmentShaderCodeWithOutput,
getPlainTypeInfo,
@@ -6,12 +6,14 @@ import {
} from '../../../util/shader.js';
import { ValidationTest } from '../validation_test.js';
+type ColorTargetState = GPUColorTargetState & { format: ColorTextureFormat };
+
const values = [0, 1, 0, 1];
export class CreateRenderPipelineValidationTest extends ValidationTest {
getDescriptor(
options: {
primitive?: GPUPrimitiveState;
- targets?: GPUColorTargetState[];
+ targets?: ColorTargetState[];
multisample?: GPUMultisampleState;
depthStencil?: GPUDepthStencilState;
fragmentShaderCode?: string;
@@ -19,17 +21,16 @@ export class CreateRenderPipelineValidationTest extends ValidationTest {
fragmentConstants?: Record<string, GPUPipelineConstantValue>;
} = {}
): GPURenderPipelineDescriptor {
- const defaultTargets: GPUColorTargetState[] = [{ format: 'rgba8unorm' }];
const {
primitive = {},
- targets = defaultTargets,
+ targets = [{ format: 'rgba8unorm' }] as const,
multisample = {},
depthStencil,
fragmentShaderCode = getFragmentShaderCodeWithOutput([
{
values,
plainType: getPlainTypeInfo(
- kTextureFormatInfo[targets[0] ? targets[0].format : 'rgba8unorm'].sampleType
+ kTextureFormatInfo[targets[0] ? targets[0].format : 'rgba8unorm'].color.type
),
componentCount: 4,
},
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/depth_stencil_state.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/depth_stencil_state.spec.ts
index eaaf78af66..403f463943 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/depth_stencil_state.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/depth_stencil_state.spec.ts
@@ -5,7 +5,11 @@ This test dedicatedly tests validation of GPUDepthStencilState of createRenderPi
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { unreachable } from '../../../../common/util/util.js';
import { kCompareFunctions, kStencilOperations } from '../../../capability_info.js';
-import { kTextureFormats, kTextureFormatInfo, kDepthStencilFormats } from '../../../format_info.js';
+import {
+ kAllTextureFormats,
+ kTextureFormatInfo,
+ kDepthStencilFormats,
+} from '../../../format_info.js';
import { getFragmentShaderCodeWithOutput } from '../../../util/shader.js';
import { CreateRenderPipelineValidationTest } from './common.js';
@@ -14,7 +18,11 @@ export const g = makeTestGroup(CreateRenderPipelineValidationTest);
g.test('format')
.desc(`The texture format in depthStencilState must be a depth/stencil format.`)
- .params(u => u.combine('isAsync', [false, true]).combine('format', kTextureFormats))
+ .params(u =>
+ u //
+ .combine('isAsync', [false, true])
+ .combine('format', kAllTextureFormats)
+ )
.beforeAllSubcases(t => {
const { format } = t.params;
const info = kTextureFormatInfo[format];
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts
index 0206431eee..c01c2ba9ef 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/fragment_state.spec.ts
@@ -10,15 +10,17 @@ import {
kMaxColorAttachmentsToTest,
} from '../../../capability_info.js';
import {
- kTextureFormats,
+ kAllTextureFormats,
kRenderableColorTextureFormats,
kTextureFormatInfo,
computeBytesPerSampleFromFormats,
+ kColorTextureFormats,
} from '../../../format_info.js';
import {
getFragmentShaderCodeWithOutput,
getPlainTypeInfo,
kDefaultFragmentShaderCode,
+ kDefaultVertexShaderCode,
} from '../../../util/shader.js';
import { kTexelRepresentationInfo } from '../../../util/texture/texel_data.js';
@@ -49,9 +51,60 @@ g.test('color_target_exists')
t.doCreateRenderPipelineTest(isAsync, false, badDescriptor);
});
+g.test('targets_format_is_color_format')
+ .desc(
+ `Tests that color target state format must be a color format, regardless of how the
+ fragment shader writes to it.`
+ )
+ .params(u =>
+ u
+ // Test all non-color texture formats, plus 'rgba8unorm' as a control case.
+ .combine('format', kAllTextureFormats)
+ .filter(({ format }) => {
+ return format === 'rgba8unorm' || !kTextureFormatInfo[format].color;
+ })
+ .combine('isAsync', [false, true])
+ .beginSubcases()
+ .combine('fragOutType', ['f32', 'u32', 'i32'] as const)
+ )
+ .beforeAllSubcases(t => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+ })
+ .fn(t => {
+ const { isAsync, format, fragOutType } = t.params;
+
+ const fragmentShaderCode = getFragmentShaderCodeWithOutput([
+ { values, plainType: fragOutType, componentCount: 4 },
+ ]);
+
+ const success = format === 'rgba8unorm' && fragOutType === 'f32';
+ t.doCreateRenderPipelineTest(isAsync, success, {
+ vertex: {
+ module: t.device.createShaderModule({ code: kDefaultVertexShaderCode }),
+ entryPoint: 'main',
+ },
+ fragment: {
+ module: t.device.createShaderModule({ code: fragmentShaderCode }),
+ entryPoint: 'main',
+ targets: [{ format }],
+ },
+ layout: 'auto',
+ });
+ });
+
g.test('targets_format_renderable')
- .desc(`Tests that color target state format must have RENDER_ATTACHMENT capability.`)
- .params(u => u.combine('isAsync', [false, true]).combine('format', kTextureFormats))
+ .desc(
+ `Tests that color target state format must have RENDER_ATTACHMENT capability
+ (tests only color formats).`
+ )
+ .params(u =>
+ u //
+ .combine('isAsync', [false, true])
+ .combine('format', kColorTextureFormats)
+ )
.beforeAllSubcases(t => {
const { format } = t.params;
const info = kTextureFormatInfo[format];
@@ -158,24 +211,12 @@ g.test('limits,maxColorAttachmentBytesPerSample,unaligned')
// become 4 and 4+4+8+16+1 > 32. Re-ordering this so the R8Unorm's are at the end, however
// is allowed: 4+8+16+1+1 < 32.
{
- formats: [
- 'r8unorm',
- 'r32float',
- 'rgba8unorm',
- 'rgba32float',
- 'r8unorm',
- ] as GPUTextureFormat[],
+ formats: ['r8unorm', 'r32float', 'rgba8unorm', 'rgba32float', 'r8unorm'],
},
{
- formats: [
- 'r32float',
- 'rgba8unorm',
- 'rgba32float',
- 'r8unorm',
- 'r8unorm',
- ] as GPUTextureFormat[],
+ formats: ['r32float', 'rgba8unorm', 'rgba32float', 'r8unorm', 'r8unorm'],
},
- ])
+ ] as const)
.beginSubcases()
.combine('isAsync', [false, true])
)
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/inter_stage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/inter_stage.spec.ts
index 91aabb0ab8..db65ba8bf4 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/inter_stage.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/inter_stage.spec.ts
@@ -3,7 +3,7 @@ Interface matching between vertex and fragment shader validation for createRende
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
-import { assert, range } from '../../../../common/util/util.js';
+import { range } from '../../../../common/util/util.js';
import { CreateRenderPipelineValidationTest } from './common.js';
@@ -97,8 +97,18 @@ g.test('location,mismatch')
});
g.test('location,superset')
- .desc(`TODO: implement after spec is settled: https://github.com/gpuweb/gpuweb/issues/2038`)
- .unimplemented();
+ .desc(`Tests that validation should succeed when vertex output is superset of fragment input`)
+ .params(u => u.combine('isAsync', [false, true]))
+ .fn(t => {
+ const { isAsync } = t.params;
+
+ const descriptor = t.getDescriptorWithStates(
+ t.getVertexStateWithOutputs(['@location(0) vout0: f32', '@location(1) vout1: f32']),
+ t.getFragmentStateWithInputs(['@location(1) fin1: f32'])
+ );
+
+ t.doCreateRenderPipelineTest(isAsync, true, descriptor);
+ });
g.test('location,subset')
.desc(`Tests that validation should fail when vertex output is a subset of fragment input.`)
@@ -159,20 +169,27 @@ g.test('interpolation_type')
{ output: '@interpolate(linear)', input: '@interpolate(perspective)' },
{ output: '@interpolate(flat)', input: '@interpolate(perspective)' },
{ output: '@interpolate(linear)', input: '@interpolate(flat)' },
- { output: '@interpolate(linear, center)', input: '@interpolate(linear, center)' },
+ {
+ output: '@interpolate(linear, center)',
+ input: '@interpolate(linear, center)',
+ _compat_success: false,
+ },
])
)
.fn(t => {
- const { isAsync, output, input, _success } = t.params;
+ const { isAsync, output, input, _success, _compat_success } = t.params;
const descriptor = t.getDescriptorWithStates(
t.getVertexStateWithOutputs([`@location(0) ${output} vout0: f32`]),
t.getFragmentStateWithInputs([`@location(0) ${input} fin0: f32`])
);
- t.doCreateRenderPipelineTest(isAsync, _success ?? output === input, descriptor);
- });
+ const shouldSucceed =
+ (_success ?? output === input) && (!t.isCompatibility || _compat_success !== false);
+ t.doCreateRenderPipelineTest(isAsync, shouldSucceed, descriptor);
+ });
+1;
g.test('interpolation_sampling')
.desc(
`Tests that validation should fail when interpolation sampling of vertex output and fragment input at the same location doesn't match.`
@@ -186,7 +203,12 @@ g.test('interpolation_sampling')
input: '@interpolate(perspective, center)',
_success: true,
},
- { output: '@interpolate(linear, center)', input: '@interpolate(linear)', _success: true },
+ {
+ output: '@interpolate(linear, center)',
+ input: '@interpolate(linear)',
+ _success: true,
+ _compat_success: false,
+ },
{ output: '@interpolate(flat)', input: '@interpolate(flat)' },
{ output: '@interpolate(perspective)', input: '@interpolate(perspective, sample)' },
{ output: '@interpolate(perspective, center)', input: '@interpolate(perspective, sample)' },
@@ -198,14 +220,17 @@ g.test('interpolation_sampling')
])
)
.fn(t => {
- const { isAsync, output, input, _success } = t.params;
+ const { isAsync, output, input, _success, _compat_success } = t.params;
const descriptor = t.getDescriptorWithStates(
t.getVertexStateWithOutputs([`@location(0) ${output} vout0: f32`]),
t.getFragmentStateWithInputs([`@location(0) ${input} fin0: f32`])
);
- t.doCreateRenderPipelineTest(isAsync, _success ?? output === input, descriptor);
+ const shouldSucceed =
+ (_success ?? output === input) && (!t.isCompatibility || _compat_success !== false);
+
+ t.doCreateRenderPipelineTest(isAsync, shouldSucceed, descriptor);
});
g.test('max_shader_variable_location')
@@ -251,9 +276,6 @@ g.test('max_components_count,output')
const numVec4 = Math.floor(numScalarComponents / 4);
const numTrailingScalars = numScalarComponents % 4;
- const numUserDefinedInterStageVariables = numTrailingScalars > 0 ? numVec4 + 1 : numVec4;
-
- assert(numUserDefinedInterStageVariables <= t.device.limits.maxInterStageShaderVariables);
const outputs = range(numVec4, i => `@location(${i}) vout${i}: vec4<f32>`);
const inputs = range(numVec4, i => `@location(${i}) fin${i}: vec4<f32>`);
@@ -280,23 +302,23 @@ g.test('max_components_count,input')
.params(u =>
u.combine('isAsync', [false, true]).combineWithParams([
// Number of user-defined input scalar components in test shader = device.limits.maxInterStageShaderComponents + numScalarDelta.
- { numScalarDelta: 0, useExtraBuiltinInputs: false, _success: true },
- { numScalarDelta: 1, useExtraBuiltinInputs: false, _success: false },
- { numScalarDelta: 0, useExtraBuiltinInputs: true, _success: false },
- { numScalarDelta: -3, useExtraBuiltinInputs: true, _success: true },
- { numScalarDelta: -2, useExtraBuiltinInputs: true, _success: false },
+ { numScalarDelta: 0, useExtraBuiltinInputs: false },
+ { numScalarDelta: 1, useExtraBuiltinInputs: false },
+ { numScalarDelta: 0, useExtraBuiltinInputs: true },
+ { numScalarDelta: -3, useExtraBuiltinInputs: true },
+ { numScalarDelta: -2, useExtraBuiltinInputs: true },
] as const)
)
.fn(t => {
- const { isAsync, numScalarDelta, useExtraBuiltinInputs, _success } = t.params;
+ const { isAsync, numScalarDelta, useExtraBuiltinInputs } = t.params;
const numScalarComponents = t.device.limits.maxInterStageShaderComponents + numScalarDelta;
+ const numExtraComponents = useExtraBuiltinInputs ? (t.isCompatibility ? 2 : 3) : 0;
+ const numUsedComponents = numScalarComponents + numExtraComponents;
+ const success = numUsedComponents <= t.device.limits.maxInterStageShaderComponents;
const numVec4 = Math.floor(numScalarComponents / 4);
const numTrailingScalars = numScalarComponents % 4;
- const numUserDefinedInterStageVariables = numTrailingScalars > 0 ? numVec4 + 1 : numVec4;
-
- assert(numUserDefinedInterStageVariables <= t.device.limits.maxInterStageShaderVariables);
const outputs = range(numVec4, i => `@location(${i}) vout${i}: vec4<f32>`);
const inputs = range(numVec4, i => `@location(${i}) fin${i}: vec4<f32>`);
@@ -310,9 +332,11 @@ g.test('max_components_count,input')
if (useExtraBuiltinInputs) {
inputs.push(
'@builtin(front_facing) front_facing_in: bool',
- '@builtin(sample_index) sample_index_in: u32',
'@builtin(sample_mask) sample_mask_in: u32'
);
+ if (!t.isCompatibility) {
+ inputs.push('@builtin(sample_index) sample_index_in: u32');
+ }
}
const descriptor = t.getDescriptorWithStates(
@@ -320,5 +344,5 @@ g.test('max_components_count,input')
t.getFragmentStateWithInputs(inputs, true)
);
- t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
+ t.doCreateRenderPipelineTest(isAsync, success, descriptor);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/misc.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/misc.spec.ts
index 1e3ccf5637..adb091a236 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/misc.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/misc.spec.ts
@@ -96,3 +96,37 @@ g.test('pipeline_layout,device_mismatch')
t.doCreateRenderPipelineTest(isAsync, !mismatched, descriptor);
});
+
+g.test('external_texture')
+ .desc('Tests createRenderPipeline() with an external_texture')
+ .fn(t => {
+ const shader = t.device.createShaderModule({
+ code: `
+ @vertex
+ fn vertexMain() -> @builtin(position) vec4f {
+ return vec4f(1);
+ }
+
+ @group(0) @binding(0) var myTexture: texture_external;
+
+ @fragment
+ fn fragmentMain() -> @location(0) vec4f {
+ let result = textureLoad(myTexture, vec2u(1, 1));
+ return vec4f(1);
+ }
+ `,
+ });
+
+ const descriptor: GPURenderPipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module: shader,
+ },
+ fragment: {
+ module: shader,
+ targets: [{ format: 'rgba8unorm' }],
+ },
+ };
+
+ t.doCreateRenderPipelineTest(false, true, descriptor);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/resource_compatibility.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/resource_compatibility.spec.ts
new file mode 100644
index 0000000000..5d6bc8d125
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/render_pipeline/resource_compatibility.spec.ts
@@ -0,0 +1,95 @@
+export const description = `
+Tests for resource compatibilty between pipeline layout and shader modules
+ `;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import {
+ kAPIResources,
+ getWGSLShaderForResource,
+ getAPIBindGroupLayoutForResource,
+ doResourcesMatch,
+} from '../utils.js';
+
+import { CreateRenderPipelineValidationTest } from './common.js';
+
+export const g = makeTestGroup(CreateRenderPipelineValidationTest);
+
+g.test('resource_compatibility')
+ .desc(
+ 'Tests validation of resource (bind group) compatibility between pipeline layout and WGSL shader'
+ )
+ .params(u =>
+ u //
+ .combine('stage', ['vertex', 'fragment'] as const)
+ .combine('apiResource', keysOf(kAPIResources))
+ .filter(t => {
+ const res = kAPIResources[t.apiResource];
+ if (t.stage === 'vertex') {
+ if (res.buffer && res.buffer.type === 'storage') {
+ return false;
+ }
+ if (res.storageTexture && res.storageTexture.access !== 'read-only') {
+ return false;
+ }
+ }
+ return true;
+ })
+ .beginSubcases()
+ .combine('isAsync', [true, false] as const)
+ .combine('wgslResource', keysOf(kAPIResources))
+ )
+ .fn(t => {
+ const apiResource = kAPIResources[t.params.apiResource];
+ const wgslResource = kAPIResources[t.params.wgslResource];
+ t.skipIf(
+ wgslResource.storageTexture !== undefined &&
+ wgslResource.storageTexture.access !== 'write-only' &&
+ !t.hasLanguageFeature('readonly_and_readwrite_storage_textures'),
+ 'Storage textures require language feature'
+ );
+ const emptyVS = `
+@vertex
+fn main() -> @builtin(position) vec4f {
+ return vec4f();
+}
+`;
+ const emptyFS = `
+@fragment
+fn main() -> @location(0) vec4f {
+ return vec4f();
+}
+`;
+
+ const code = getWGSLShaderForResource(t.params.stage, wgslResource);
+ const vsCode = t.params.stage === 'vertex' ? code : emptyVS;
+ const fsCode = t.params.stage === 'fragment' ? code : emptyFS;
+ const gpuStage: GPUShaderStageFlags =
+ t.params.stage === 'vertex' ? GPUShaderStage.VERTEX : GPUShaderStage.FRAGMENT;
+ const layout = t.device.createPipelineLayout({
+ bindGroupLayouts: [getAPIBindGroupLayoutForResource(t.device, gpuStage, apiResource)],
+ });
+
+ const descriptor = {
+ layout,
+ vertex: {
+ module: t.device.createShaderModule({
+ code: vsCode,
+ }),
+ entryPoint: 'main',
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code: fsCode,
+ }),
+ entryPoint: 'main',
+ targets: [{ format: 'rgba8unorm' }] as const,
+ },
+ };
+
+ t.doCreateRenderPipelineTest(
+ t.params.isAsync,
+ doResourcesMatch(apiResource, wgslResource),
+ descriptor
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_pass_encoder.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_pass_encoder.spec.ts
index d316f26c06..5114cbb266 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_pass_encoder.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_pass_encoder.spec.ts
@@ -13,11 +13,18 @@ import {
} from '../../../../format_info.js';
import { ValidationTest } from '../../validation_test.js';
-type TextureBindingType = 'sampled-texture' | 'multisampled-texture' | 'writeonly-storage-texture';
+type TextureBindingType =
+ | 'sampled-texture'
+ | 'multisampled-texture'
+ | 'writeonly-storage-texture'
+ | 'readonly-storage-texture'
+ | 'readwrite-storage-texture';
const kTextureBindingTypes = [
'sampled-texture',
'multisampled-texture',
'writeonly-storage-texture',
+ 'readonly-storage-texture',
+ 'readwrite-storage-texture',
] as const;
const SIZE = 32;
@@ -39,7 +46,7 @@ class TextureUsageTracking extends ValidationTest {
arrayLayerCount = 1,
mipLevelCount = 1,
sampleCount = 1,
- format = 'rgba8unorm',
+ format = 'r32float',
usage = GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
} = options;
@@ -75,6 +82,14 @@ class TextureUsageTracking extends ValidationTest {
assert(format !== undefined);
entry = { storageTexture: { access: 'write-only', format, viewDimension } };
break;
+ case 'readonly-storage-texture':
+ assert(format !== undefined);
+ entry = { storageTexture: { access: 'read-only', format, viewDimension } };
+ break;
+ case 'readwrite-storage-texture':
+ assert(format !== undefined);
+ entry = { storageTexture: { access: 'read-write', format, viewDimension } };
+ break;
}
return this.device.createBindGroupLayout({
@@ -107,7 +122,7 @@ class TextureUsageTracking extends ValidationTest {
depthStencilFormat?: GPUTextureFormat
) {
const bundleEncoder = this.device.createRenderBundleEncoder({
- colorFormats: ['rgba8unorm'],
+ colorFormats: ['r32float'],
depthStencilFormat,
});
bundleEncoder.setBindGroup(binding, bindGroup);
@@ -129,16 +144,21 @@ class TextureUsageTracking extends ValidationTest {
}
/**
- * Create two bind groups. Resource usages conflict between these two bind groups. But resource
- * usage inside each bind group doesn't conflict.
+ * Create two bind groups with one texture view.
*/
- makeConflictingBindGroups() {
+ makeTwoBindGroupsWithOneTextureView(usage1: TextureBindingType, usage2: TextureBindingType) {
const view = this.createTexture({
usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING,
}).createView();
const bindGroupLayouts = [
- this.createBindGroupLayout(0, 'sampled-texture', '2d'),
- this.createBindGroupLayout(0, 'writeonly-storage-texture', '2d', { format: 'rgba8unorm' }),
+ this.createBindGroupLayout(0, usage1, '2d', {
+ sampleType: 'unfilterable-float',
+ format: 'r32float',
+ }),
+ this.createBindGroupLayout(0, usage2, '2d', {
+ sampleType: 'unfilterable-float',
+ format: 'r32float',
+ }),
];
return {
bindGroupLayouts,
@@ -155,14 +175,21 @@ class TextureUsageTracking extends ValidationTest {
};
}
- testValidationScope(compute: boolean): {
+ testValidationScope(
+ compute: boolean,
+ usage1: TextureBindingType,
+ usage2: TextureBindingType
+ ): {
bindGroup0: GPUBindGroup;
bindGroup1: GPUBindGroup;
encoder: GPUCommandEncoder;
pass: GPURenderPassEncoder | GPUComputePassEncoder;
pipeline: GPURenderPipeline | GPUComputePipeline;
} {
- const { bindGroupLayouts, bindGroups } = this.makeConflictingBindGroups();
+ const { bindGroupLayouts, bindGroups } = this.makeTwoBindGroupsWithOneTextureView(
+ usage1,
+ usage2
+ );
const encoder = this.device.createCommandEncoder();
const pass = compute
@@ -175,7 +202,7 @@ class TextureUsageTracking extends ValidationTest {
});
const pipeline = compute
? this.createNoOpComputePipeline(pipelineLayout)
- : this.createNoOpRenderPipeline(pipelineLayout);
+ : this.createNoOpRenderPipeline(pipelineLayout, 'r32float');
return {
bindGroup0: bindGroups[0],
bindGroup1: bindGroups[1],
@@ -237,6 +264,8 @@ g.test('subresources_and_binding_types_combination_for_color')
[
{ _usageOK: true, type0: 'sampled-texture', type1: 'sampled-texture' },
{ _usageOK: false, type0: 'sampled-texture', type1: 'writeonly-storage-texture' },
+ { _usageOK: true, type0: 'sampled-texture', type1: 'readonly-storage-texture' },
+ { _usageOK: false, type0: 'sampled-texture', type1: 'readwrite-storage-texture' },
{ _usageOK: false, type0: 'sampled-texture', type1: 'render-target' },
// Race condition upon multiple writable storage texture is valid.
// For p.compute === true, fails at pass.dispatch because aliasing exists.
@@ -245,7 +274,34 @@ g.test('subresources_and_binding_types_combination_for_color')
type0: 'writeonly-storage-texture',
type1: 'writeonly-storage-texture',
},
+ {
+ _usageOK: true,
+ type0: 'readonly-storage-texture',
+ type1: 'readonly-storage-texture',
+ },
+ {
+ _usageOK: !p.compute,
+ type0: 'readwrite-storage-texture',
+ type1: 'readwrite-storage-texture',
+ },
+ {
+ _usageOK: false,
+ type0: 'readonly-storage-texture',
+ type1: 'writeonly-storage-texture',
+ },
+ {
+ _usageOK: false,
+ type0: 'readonly-storage-texture',
+ type1: 'readwrite-storage-texture',
+ },
+ {
+ _usageOK: false,
+ type0: 'writeonly-storage-texture',
+ type1: 'readwrite-storage-texture',
+ },
+ { _usageOK: false, type0: 'readonly-storage-texture', type1: 'render-target' },
{ _usageOK: false, type0: 'writeonly-storage-texture', type1: 'render-target' },
+ { _usageOK: false, type0: 'readwrite-storage-texture', type1: 'render-target' },
{ _usageOK: false, type0: 'render-target', type1: 'render-target' },
] as const
)
@@ -428,6 +484,11 @@ g.test('subresources_and_binding_types_combination_for_color')
_resourceSuccess,
} = t.params;
+ t.skipIf(
+ t.isCompatibility,
+ 'multiple views of the same texture in a single draw/dispatch are not supported in compat, nor are sub ranges of layers'
+ );
+
const texture = t.createTexture({
arrayLayerCount: TOTAL_LAYERS,
mipLevelCount: TOTAL_LEVELS,
@@ -497,9 +558,13 @@ g.test('subresources_and_binding_types_combination_for_color')
const bgls: GPUBindGroupLayout[] = [];
// Create bind groups. Set bind groups in pass directly or set bind groups in bundle.
- const storageTextureFormat0 = type0 === 'sampled-texture' ? undefined : 'rgba8unorm';
+ const storageTextureFormat0 = type0 === 'sampled-texture' ? undefined : 'r32float';
+ const sampleType0 = type0 === 'sampled-texture' ? 'unfilterable-float' : undefined;
- const bgl0 = t.createBindGroupLayout(0, type0, dimension0, { format: storageTextureFormat0 });
+ const bgl0 = t.createBindGroupLayout(0, type0, dimension0, {
+ format: storageTextureFormat0,
+ sampleType: sampleType0,
+ });
const bindGroup0 = t.device.createBindGroup({
layout: bgl0,
entries: [{ binding: 0, resource: view0 }],
@@ -513,10 +578,11 @@ g.test('subresources_and_binding_types_combination_for_color')
pass.setBindGroup(0, bindGroup0);
}
if (type1 !== 'render-target') {
- const storageTextureFormat1 = type1 === 'sampled-texture' ? undefined : 'rgba8unorm';
-
+ const storageTextureFormat1 = type1 === 'sampled-texture' ? undefined : 'r32float';
+ const sampleType1 = type1 === 'sampled-texture' ? 'unfilterable-float' : undefined;
const bgl1 = t.createBindGroupLayout(1, type1, dimension1, {
format: storageTextureFormat1,
+ sampleType: sampleType1,
});
const bindGroup1 = t.device.createBindGroup({
layout: bgl1,
@@ -653,6 +719,8 @@ g.test('subresources_and_binding_types_combination_for_aspect')
_usageSuccess,
} = t.params;
+ t.skipIf(t.isCompatibility, 'sub ranges of layers are not supported in compat mode');
+
const texture = t.createTexture({
arrayLayerCount: TOTAL_LAYERS,
mipLevelCount: TOTAL_LEVELS,
@@ -782,9 +850,21 @@ g.test('shader_stages_and_visibility,storage_write')
GPUConst.ShaderStage.COMPUTE,
])
.combine('writeVisibility', [0, GPUConst.ShaderStage.FRAGMENT, GPUConst.ShaderStage.COMPUTE])
+ .combine('readEntry', [
+ { texture: { sampleType: 'unfilterable-float' } },
+ { storageTexture: { access: 'read-only', format: 'r32float' } },
+ ] as const)
+ .combine('storageWriteAccess', ['write-only', 'read-write'] as const)
)
.fn(t => {
- const { compute, readVisibility, writeVisibility, secondUseConflicts } = t.params;
+ const {
+ compute,
+ readEntry,
+ storageWriteAccess,
+ readVisibility,
+ writeVisibility,
+ secondUseConflicts,
+ } = t.params;
const usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING;
const view = t.createTexture({ usage }).createView();
@@ -792,11 +872,11 @@ g.test('shader_stages_and_visibility,storage_write')
const bgl = t.device.createBindGroupLayout({
entries: [
- { binding: 0, visibility: readVisibility, texture: {} },
+ { binding: 0, visibility: readVisibility, ...readEntry },
{
binding: 1,
visibility: writeVisibility,
- storageTexture: { access: 'write-only', format: 'rgba8unorm' },
+ storageTexture: { access: storageWriteAccess, format: 'r32float' },
},
],
});
@@ -851,19 +931,23 @@ g.test('shader_stages_and_visibility,attachment_write')
GPUConst.ShaderStage.FRAGMENT,
GPUConst.ShaderStage.COMPUTE,
])
+ .combine('readEntry', [
+ { texture: { sampleType: 'unfilterable-float' } },
+ { storageTexture: { access: 'read-only', format: 'r32float' } },
+ ] as const)
)
.fn(t => {
- const { readVisibility, secondUseConflicts } = t.params;
+ const { readVisibility, readEntry, secondUseConflicts } = t.params;
- // writeonly-storage-texture binding type is not supported in vertex stage. So, this test
- // uses writeonly-storage-texture binding as writable binding upon the same subresource if
- // vertex stage is not included. Otherwise, it uses output attachment instead.
- const usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT;
+ const usage =
+ GPUTextureUsage.TEXTURE_BINDING |
+ GPUTextureUsage.RENDER_ATTACHMENT |
+ GPUTextureUsage.STORAGE_BINDING;
const view = t.createTexture({ usage }).createView();
const view2 = secondUseConflicts ? view : t.createTexture({ usage }).createView();
const bgl = t.device.createBindGroupLayout({
- entries: [{ binding: 0, visibility: readVisibility, texture: {} }],
+ entries: [{ binding: 0, visibility: readVisibility, ...readEntry }],
});
const bindGroup = t.device.createBindGroup({
layout: bgl,
@@ -898,8 +982,10 @@ g.test('replaced_binding')
.combine('compute', [false, true])
.combine('callDrawOrDispatch', [false, true])
.combine('entry', [
- { texture: {} },
- { storageTexture: { access: 'write-only', format: 'rgba8unorm' } },
+ { texture: { sampleType: 'unfilterable-float' } },
+ { storageTexture: { access: 'read-only', format: 'r32float' } },
+ { storageTexture: { access: 'write-only', format: 'r32float' } },
+ { storageTexture: { access: 'read-write', format: 'r32float' } },
] as const)
)
.fn(t => {
@@ -912,7 +998,11 @@ g.test('replaced_binding')
// Create bindGroup0. It has two bindings. These two bindings use different views/subresources.
const bglEntries0: GPUBindGroupLayoutEntry[] = [
- { binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: {} },
+ {
+ binding: 0,
+ visibility: GPUShaderStage.FRAGMENT,
+ texture: { sampleType: 'unfilterable-float' },
+ },
{
binding: 1,
visibility: GPUShaderStage.FRAGMENT,
@@ -930,7 +1020,9 @@ g.test('replaced_binding')
// Create bindGroup1. It has one binding, which use the same view/subresource of a binding in
// bindGroup0. So it may or may not conflicts with that binding in bindGroup0.
- const bindGroup1 = t.createBindGroup(0, sampledStorageView, 'sampled-texture', '2d', undefined);
+ const bindGroup1 = t.createBindGroup(0, sampledStorageView, 'sampled-texture', '2d', {
+ sampleType: 'unfilterable-float',
+ });
const encoder = t.device.createCommandEncoder();
const pass = compute
@@ -941,7 +1033,9 @@ g.test('replaced_binding')
// But bindings in bindGroup0 should be validated too.
pass.setBindGroup(0, bindGroup0);
if (callDrawOrDispatch) {
- const pipeline = compute ? t.createNoOpComputePipeline() : t.createNoOpRenderPipeline();
+ const pipeline = compute
+ ? t.createNoOpComputePipeline()
+ : t.createNoOpRenderPipeline('auto', 'r32float');
t.setPipeline(pass, pipeline);
t.issueDrawOrDispatch(pass);
}
@@ -951,7 +1045,9 @@ g.test('replaced_binding')
// MAINTENANCE_TODO: If the Compatible Usage List
// (https://gpuweb.github.io/gpuweb/#compatible-usage-list) gets programmatically defined in
// capability_info, use it here, instead of this logic, for clarity.
- let success = entry.storageTexture?.access !== 'write-only';
+ let success =
+ entry.storageTexture?.access !== 'write-only' &&
+ entry.storageTexture?.access !== 'read-write';
// Replaced bindings should not be validated in compute pass, because validation only occurs
// inside dispatchWorkgroups() which only looks at the current resource usages.
success ||= compute;
@@ -981,7 +1077,9 @@ g.test('bindings_in_bundle')
case 'multisampled-texture':
case 'sampled-texture':
return 'TEXTURE_BINDING' as const;
+ case 'readonly-storage-texture':
case 'writeonly-storage-texture':
+ case 'readwrite-storage-texture':
return 'STORAGE_BINDING' as const;
case 'render-target':
return 'RENDER_ATTACHMENT' as const;
@@ -1033,17 +1131,17 @@ g.test('bindings_in_bundle')
const bindGroups: GPUBindGroup[] = [];
if (type0 !== 'render-target') {
- const binding0TexFormat = type0 === 'sampled-texture' ? undefined : 'rgba8unorm';
+ const binding0TexFormat = type0 === 'sampled-texture' ? undefined : 'r32float';
bindGroups[0] = t.createBindGroup(0, view, type0, '2d', {
format: binding0TexFormat,
- sampleType: _sampleCount && 'unfilterable-float',
+ sampleType: 'unfilterable-float',
});
}
if (type1 !== 'render-target') {
- const binding1TexFormat = type1 === 'sampled-texture' ? undefined : 'rgba8unorm';
+ const binding1TexFormat = type1 === 'sampled-texture' ? undefined : 'r32float';
bindGroups[1] = t.createBindGroup(1, view, type1, '2d', {
format: binding1TexFormat,
- sampleType: _sampleCount && 'unfilterable-float',
+ sampleType: 'unfilterable-float',
});
}
@@ -1062,7 +1160,7 @@ g.test('bindings_in_bundle')
// 'render-target').
if (bindingsInBundle[i]) {
const bundleEncoder = t.device.createRenderBundleEncoder({
- colorFormats: ['rgba8unorm'],
+ colorFormats: ['r32float'],
});
bundleEncoder.setBindGroup(i, bindGroups[i]);
const bundleInPass = bundleEncoder.finish();
@@ -1078,6 +1176,7 @@ g.test('bindings_in_bundle')
switch (t) {
case 'sampled-texture':
case 'multisampled-texture':
+ case 'readonly-storage-texture':
return true;
default:
return false;
@@ -1089,7 +1188,8 @@ g.test('bindings_in_bundle')
success = true;
}
- if (type0 === 'writeonly-storage-texture' && type1 === 'writeonly-storage-texture') {
+ // Writable storage textures (write-only and read-write storage textures) cannot be aliased.
+ if (type0 === type1) {
success = true;
}
@@ -1110,6 +1210,8 @@ g.test('unused_bindings_in_pipeline')
.params(u =>
u
.combine('compute', [false, true])
+ .combine('readOnlyUsage', ['sampled-texture', 'readonly-storage-texture'] as const)
+ .combine('writableUsage', ['writeonly-storage-texture', 'readwrite-storage-texture'] as const)
.combine('useBindGroup0', [false, true])
.combine('useBindGroup1', [false, true])
.combine('setBindGroupsOrder', ['common', 'reversed'] as const)
@@ -1119,41 +1221,49 @@ g.test('unused_bindings_in_pipeline')
.fn(t => {
const {
compute,
+ readOnlyUsage,
+ writableUsage,
useBindGroup0,
useBindGroup1,
setBindGroupsOrder,
setPipeline,
callDrawOrDispatch,
} = t.params;
+ if (writableUsage === 'readwrite-storage-texture') {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+ }
+
const view = t
.createTexture({ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING })
.createView();
- const bindGroup0 = t.createBindGroup(0, view, 'sampled-texture', '2d', {
- format: 'rgba8unorm',
+ const bindGroup0 = t.createBindGroup(0, view, readOnlyUsage, '2d', {
+ sampleType: 'unfilterable-float',
+ format: 'r32float',
});
- const bindGroup1 = t.createBindGroup(0, view, 'writeonly-storage-texture', '2d', {
- format: 'rgba8unorm',
+ const bindGroup1 = t.createBindGroup(0, view, writableUsage, '2d', {
+ format: 'r32float',
});
+ const writeAccess = writableUsage === 'writeonly-storage-texture' ? 'write' : 'read_write';
const wgslVertex = `@vertex fn main() -> @builtin(position) vec4<f32> {
return vec4<f32>();
}`;
const wgslFragment = pp`
${pp._if(useBindGroup0)}
- @group(0) @binding(0) var image0 : texture_storage_2d<rgba8unorm, write>;
+ @group(0) @binding(0) var image0 : texture_storage_2d<r32float, ${writeAccess}>;
${pp._endif}
${pp._if(useBindGroup1)}
- @group(1) @binding(0) var image1 : texture_storage_2d<rgba8unorm, write>;
+ @group(1) @binding(0) var image1 : texture_storage_2d<r32float, ${writeAccess}>;
${pp._endif}
@fragment fn main() {}
`;
const wgslCompute = pp`
${pp._if(useBindGroup0)}
- @group(0) @binding(0) var image0 : texture_storage_2d<rgba8unorm, write>;
+ @group(0) @binding(0) var image0 : texture_storage_2d<r32float, ${writeAccess}>;
${pp._endif}
${pp._if(useBindGroup1)}
- @group(1) @binding(0) var image1 : texture_storage_2d<rgba8unorm, write>;
+ @group(1) @binding(0) var image1 : texture_storage_2d<r32float, ${writeAccess}>;
${pp._endif}
@compute @workgroup_size(1) fn main() {}
`;
@@ -1181,7 +1291,7 @@ g.test('unused_bindings_in_pipeline')
code: wgslFragment,
}),
entryPoint: 'main',
- targets: [{ format: 'rgba8unorm', writeMask: 0 }],
+ targets: [{ format: 'r32float', writeMask: 0 }],
},
primitive: { topology: 'triangle-list' },
});
@@ -1237,14 +1347,28 @@ g.test('scope,dispatch')
.params(u =>
u
.combine('dispatch', ['none', 'direct', 'indirect'])
+ .expandWithParams(
+ p =>
+ [
+ { usage1: 'sampled-texture', usage2: 'writeonly-storage-texture' },
+ { usage1: 'sampled-texture', usage2: 'readwrite-storage-texture' },
+ { usage1: 'readonly-storage-texture', usage2: 'writeonly-storage-texture' },
+ { usage1: 'readonly-storage-texture', usage2: 'readwrite-storage-texture' },
+ { usage1: 'writeonly-storage-texture', usage2: 'readwrite-storage-texture' },
+ ] as const
+ )
.beginSubcases()
.expand('setBindGroup0', p => (p.dispatch ? [true] : [false, true]))
.expand('setBindGroup1', p => (p.dispatch ? [true] : [false, true]))
)
.fn(t => {
- const { dispatch, setBindGroup0, setBindGroup1 } = t.params;
+ const { dispatch, usage1, usage2, setBindGroup0, setBindGroup1 } = t.params;
- const { bindGroup0, bindGroup1, encoder, pass, pipeline } = t.testValidationScope(true);
+ const { bindGroup0, bindGroup1, encoder, pass, pipeline } = t.testValidationScope(
+ true,
+ usage1,
+ usage2
+ );
assert(pass instanceof GPUComputePassEncoder);
t.setPipeline(pass, pipeline);
@@ -1284,11 +1408,21 @@ g.test('scope,basic,render')
u //
.combine('setBindGroup0', [false, true])
.combine('setBindGroup1', [false, true])
+ .expandWithParams(
+ p =>
+ [
+ { usage1: 'sampled-texture', usage2: 'writeonly-storage-texture' },
+ { usage1: 'sampled-texture', usage2: 'readwrite-storage-texture' },
+ { usage1: 'readonly-storage-texture', usage2: 'writeonly-storage-texture' },
+ { usage1: 'readonly-storage-texture', usage2: 'readwrite-storage-texture' },
+ { usage1: 'writeonly-storage-texture', usage2: 'readwrite-storage-texture' },
+ ] as const
+ )
)
.fn(t => {
- const { setBindGroup0, setBindGroup1 } = t.params;
+ const { setBindGroup0, setBindGroup1, usage1, usage2 } = t.params;
- const { bindGroup0, bindGroup1, encoder, pass } = t.testValidationScope(false);
+ const { bindGroup0, bindGroup1, encoder, pass } = t.testValidationScope(false, usage1, usage2);
assert(pass instanceof GPURenderPassEncoder);
if (setBindGroup0) pass.setBindGroup(0, bindGroup0);
@@ -1308,11 +1442,22 @@ g.test('scope,pass_boundary,compute')
boundary in between. This should always be valid.
`
)
- .paramsSubcasesOnly(u => u.combine('splitPass', [false, true]))
+ .paramsSubcasesOnly(u =>
+ u.combine('splitPass', [false, true]).expandWithParams(
+ p =>
+ [
+ { usage1: 'sampled-texture', usage2: 'writeonly-storage-texture' },
+ { usage1: 'sampled-texture', usage2: 'readwrite-storage-texture' },
+ { usage1: 'readonly-storage-texture', usage2: 'writeonly-storage-texture' },
+ { usage1: 'readonly-storage-texture', usage2: 'readwrite-storage-texture' },
+ { usage1: 'writeonly-storage-texture', usage2: 'readwrite-storage-texture' },
+ ] as const
+ )
+ )
.fn(t => {
- const { splitPass } = t.params;
+ const { splitPass, usage1, usage2 } = t.params;
- const { bindGroupLayouts, bindGroups } = t.makeConflictingBindGroups();
+ const { bindGroupLayouts, bindGroups } = t.makeTwoBindGroupsWithOneTextureView(usage1, usage2);
const encoder = t.device.createCommandEncoder();
@@ -1355,23 +1500,35 @@ g.test('scope,pass_boundary,render')
u //
.combine('splitPass', [false, true])
.combine('draw', [false, true])
+ .expandWithParams(
+ p =>
+ [
+ { usage1: 'sampled-texture', usage2: 'writeonly-storage-texture' },
+ { usage1: 'sampled-texture', usage2: 'readwrite-storage-texture' },
+ { usage1: 'readonly-storage-texture', usage2: 'writeonly-storage-texture' },
+ { usage1: 'readonly-storage-texture', usage2: 'readwrite-storage-texture' },
+ { usage1: 'writeonly-storage-texture', usage2: 'readwrite-storage-texture' },
+ ] as const
+ )
)
.fn(t => {
- const { splitPass, draw } = t.params;
+ const { splitPass, draw, usage1, usage2 } = t.params;
- const { bindGroupLayouts, bindGroups } = t.makeConflictingBindGroups();
+ const { bindGroupLayouts, bindGroups } = t.makeTwoBindGroupsWithOneTextureView(usage1, usage2);
const encoder = t.device.createCommandEncoder();
const pipelineUsingBG0 = t.createNoOpRenderPipeline(
t.device.createPipelineLayout({
bindGroupLayouts: [bindGroupLayouts[0]],
- })
+ }),
+ 'r32float'
);
const pipelineUsingBG1 = t.createNoOpRenderPipeline(
t.device.createPipelineLayout({
bindGroupLayouts: [bindGroupLayouts[1]],
- })
+ }),
+ 'r32float'
);
const attachment = t.createTexture().createView();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_common.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_common.spec.ts
index 0c41098556..120aadfb07 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_common.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_common.spec.ts
@@ -6,6 +6,21 @@ import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { assert, unreachable } from '../../../../../common/util/util.js';
import { ValidationTest } from '../../validation_test.js';
+export type TextureBindingType =
+ | 'sampled-texture'
+ | 'writeonly-storage-texture'
+ | 'readonly-storage-texture'
+ | 'readwrite-storage-texture';
+export const kTextureBindingTypes = [
+ 'sampled-texture',
+ 'writeonly-storage-texture',
+ 'readonly-storage-texture',
+ 'readwrite-storage-texture',
+] as const;
+export function IsReadOnlyTextureBindingType(t: TextureBindingType): boolean {
+ return t === 'sampled-texture' || t === 'readonly-storage-texture';
+}
+
class F extends ValidationTest {
getColorAttachment(
texture: GPUTexture,
@@ -23,21 +38,35 @@ class F extends ValidationTest {
createBindGroupForTest(
textureView: GPUTextureView,
- textureUsage: 'texture' | 'storage',
- sampleType: 'float' | 'depth' | 'uint'
+ textureUsage: TextureBindingType,
+ sampleType: 'unfilterable-float' | 'depth' | 'uint'
) {
const bindGroupLayoutEntry: GPUBindGroupLayoutEntry = {
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
};
switch (textureUsage) {
- case 'texture':
+ case 'sampled-texture':
bindGroupLayoutEntry.texture = { viewDimension: '2d-array', sampleType };
break;
- case 'storage':
+ case 'readonly-storage-texture':
+ bindGroupLayoutEntry.storageTexture = {
+ access: 'read-only',
+ format: 'r32float',
+ viewDimension: '2d-array',
+ };
+ break;
+ case 'readwrite-storage-texture':
+ bindGroupLayoutEntry.storageTexture = {
+ access: 'read-write',
+ format: 'r32float',
+ viewDimension: '2d-array',
+ };
+ break;
+ case 'writeonly-storage-texture':
bindGroupLayoutEntry.storageTexture = {
access: 'write-only',
- format: 'rgba8unorm',
+ format: 'r32float',
viewDimension: '2d-array',
};
break;
@@ -89,7 +118,7 @@ g.test('subresources,color_attachments')
const { layer0, level0, layer1, level1, inSamePass } = t.params;
const texture = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
size: [kTextureSize, kTextureSize, kTextureLayers],
mipLevelCount: kTextureLevels,
@@ -152,8 +181,8 @@ g.test('subresources,color_attachment_and_bind_group')
{ bgLayer: 1, bgLayerCount: 1 },
{ bgLayer: 1, bgLayerCount: 2 },
])
- .combine('bgUsage', ['texture', 'storage'] as const)
- .unless(t => t.bgUsage === 'storage' && t.bgLevelCount > 1)
+ .combine('bgUsage', kTextureBindingTypes)
+ .unless(t => t.bgUsage !== 'sampled-texture' && t.bgLevelCount > 1)
.combine('inSamePass', [true, false])
)
.fn(t => {
@@ -169,7 +198,7 @@ g.test('subresources,color_attachment_and_bind_group')
} = t.params;
const texture = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage:
GPUTextureUsage.RENDER_ATTACHMENT |
GPUTextureUsage.TEXTURE_BINDING |
@@ -184,7 +213,7 @@ g.test('subresources,color_attachment_and_bind_group')
baseMipLevel: bgLevel,
mipLevelCount: bgLevelCount,
});
- const bindGroup = t.createBindGroupForTest(bindGroupView, bgUsage, 'float');
+ const bindGroup = t.createBindGroupForTest(bindGroupView, bgUsage, 'unfilterable-float');
const colorAttachment = t.getColorAttachment(texture, {
dimension: '2d',
@@ -205,7 +234,7 @@ g.test('subresources,color_attachment_and_bind_group')
renderPass.end();
const texture2 = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
size: [kTextureSize, kTextureSize, 1],
mipLevelCount: 1,
@@ -261,7 +290,8 @@ g.test('subresources,depth_stencil_attachment_and_bind_group')
{ bgLayer: 1, bgLayerCount: 2 },
])
.beginSubcases()
- .combine('dsReadOnly', [true, false])
+ .combine('depthReadOnly', [true, false])
+ .combine('stencilReadOnly', [true, false])
.combine('bgAspect', ['depth-only', 'stencil-only'] as const)
.combine('inSamePass', [true, false])
)
@@ -273,7 +303,8 @@ g.test('subresources,depth_stencil_attachment_and_bind_group')
bgLevelCount,
bgLayer,
bgLayerCount,
- dsReadOnly,
+ depthReadOnly,
+ stencilReadOnly,
bgAspect,
inSamePass,
} = t.params;
@@ -293,7 +324,7 @@ g.test('subresources,depth_stencil_attachment_and_bind_group')
aspect: bgAspect,
});
const sampleType = bgAspect === 'depth-only' ? 'depth' : 'uint';
- const bindGroup = t.createBindGroupForTest(bindGroupView, 'texture', sampleType);
+ const bindGroup = t.createBindGroupForTest(bindGroupView, 'sampled-texture', sampleType);
const attachmentView = texture.createView({
dimension: '2d',
@@ -304,12 +335,12 @@ g.test('subresources,depth_stencil_attachment_and_bind_group')
});
const depthStencilAttachment: GPURenderPassDepthStencilAttachment = {
view: attachmentView,
- depthReadOnly: dsReadOnly,
- depthLoadOp: dsReadOnly ? undefined : 'load',
- depthStoreOp: dsReadOnly ? undefined : 'store',
- stencilReadOnly: dsReadOnly,
- stencilLoadOp: dsReadOnly ? undefined : 'load',
- stencilStoreOp: dsReadOnly ? undefined : 'store',
+ depthReadOnly,
+ depthLoadOp: depthReadOnly ? undefined : 'load',
+ depthStoreOp: depthReadOnly ? undefined : 'store',
+ stencilReadOnly,
+ stencilLoadOp: stencilReadOnly ? undefined : 'load',
+ stencilStoreOp: stencilReadOnly ? undefined : 'store',
};
const encoder = t.device.createCommandEncoder();
@@ -350,8 +381,11 @@ g.test('subresources,depth_stencil_attachment_and_bind_group')
bgLayer + bgLayerCount - 1
);
const isNotOverlapped = isMipLevelNotOverlapped || isArrayLayerNotOverlapped;
+ const readonly =
+ (bgAspect === 'stencil-only' && stencilReadOnly) ||
+ (bgAspect === 'depth-only' && depthReadOnly);
- const success = !inSamePass || isNotOverlapped || dsReadOnly;
+ const success = !inSamePass || isNotOverlapped || readonly;
t.expectValidationError(() => {
encoder.finish();
}, !success);
@@ -388,20 +422,21 @@ g.test('subresources,multiple_bind_groups')
{ base: 1, count: 1 },
{ base: 1, count: 2 },
])
- .combine('bgUsage0', ['texture', 'storage'] as const)
- .combine('bgUsage1', ['texture', 'storage'] as const)
+ .combine('bgUsage0', kTextureBindingTypes)
+ .combine('bgUsage1', kTextureBindingTypes)
.unless(
t =>
- (t.bgUsage0 === 'storage' && t.bg0Levels.count > 1) ||
- (t.bgUsage1 === 'storage' && t.bg1Levels.count > 1)
+ (t.bgUsage0 !== 'sampled-texture' && t.bg0Levels.count > 1) ||
+ (t.bgUsage1 !== 'sampled-texture' && t.bg1Levels.count > 1)
)
+ .beginSubcases()
.combine('inSamePass', [true, false])
)
.fn(t => {
const { bg0Levels, bg0Layers, bg1Levels, bg1Layers, bgUsage0, bgUsage1, inSamePass } = t.params;
const texture = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING,
size: [kTextureSize, kTextureSize, kTextureLayers],
mipLevelCount: kTextureLevels,
@@ -420,11 +455,11 @@ g.test('subresources,multiple_bind_groups')
baseMipLevel: bg1Levels.base,
mipLevelCount: bg1Levels.count,
});
- const bindGroup0 = t.createBindGroupForTest(bg0, bgUsage0, 'float');
- const bindGroup1 = t.createBindGroupForTest(bg1, bgUsage1, 'float');
+ const bindGroup0 = t.createBindGroupForTest(bg0, bgUsage0, 'unfilterable-float');
+ const bindGroup1 = t.createBindGroupForTest(bg1, bgUsage1, 'unfilterable-float');
const colorTexture = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
size: [kTextureSize, kTextureSize, 1],
mipLevelCount: 1,
@@ -449,6 +484,8 @@ g.test('subresources,multiple_bind_groups')
renderPass2.end();
}
+ const bothReadOnly =
+ IsReadOnlyTextureBindingType(bgUsage0) && IsReadOnlyTextureBindingType(bgUsage1);
const isMipLevelNotOverlapped = t.isRangeNotOverlapped(
bg0Levels.base,
bg0Levels.base + bg0Levels.count - 1,
@@ -463,7 +500,7 @@ g.test('subresources,multiple_bind_groups')
);
const isNotOverlapped = isMipLevelNotOverlapped || isArrayLayerNotOverlapped;
- const success = !inSamePass || isNotOverlapped || bgUsage0 === bgUsage1;
+ const success = !inSamePass || bothReadOnly || isNotOverlapped || bgUsage0 === bgUsage1;
t.expectValidationError(() => {
encoder.finish();
}, !success);
@@ -531,8 +568,8 @@ g.test('subresources,depth_stencil_texture_in_bind_groups')
const sampleType0 = aspect0 === 'depth-only' ? 'depth' : 'uint';
const sampleType1 = aspect1 === 'depth-only' ? 'depth' : 'uint';
- const bindGroup0 = t.createBindGroupForTest(bindGroupView0, 'texture', sampleType0);
- const bindGroup1 = t.createBindGroupForTest(bindGroupView1, 'texture', sampleType1);
+ const bindGroup0 = t.createBindGroupForTest(bindGroupView0, 'sampled-texture', sampleType0);
+ const bindGroup1 = t.createBindGroupForTest(bindGroupView1, 'sampled-texture', sampleType1);
const colorTexture = t.device.createTexture({
format: 'rgba8unorm',
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.ts
index 1b80a2f73e..db18a0140f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.ts
@@ -5,11 +5,16 @@ Texture Usages Validation Tests on All Kinds of WebGPU Subresource Usage Scopes.
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { unreachable } from '../../../../../common/util/util.js';
import { ValidationTest } from '../../validation_test.js';
+import {
+ TextureBindingType,
+ kTextureBindingTypes,
+ IsReadOnlyTextureBindingType,
+} from '../texture/in_render_common.spec.js';
class F extends ValidationTest {
createBindGroupLayoutForTest(
- textureUsage: 'texture' | 'storage',
- sampleType: 'float' | 'depth' | 'uint',
+ textureUsage: TextureBindingType,
+ sampleType: 'unfilterable-float' | 'depth' | 'uint',
visibility: GPUShaderStage['FRAGMENT'] | GPUShaderStage['COMPUTE'] = GPUShaderStage['FRAGMENT']
): GPUBindGroupLayout {
const bindGroupLayoutEntry: GPUBindGroupLayoutEntry = {
@@ -18,13 +23,27 @@ class F extends ValidationTest {
};
switch (textureUsage) {
- case 'texture':
+ case 'sampled-texture':
bindGroupLayoutEntry.texture = { viewDimension: '2d-array', sampleType };
break;
- case 'storage':
+ case 'readonly-storage-texture':
+ bindGroupLayoutEntry.storageTexture = {
+ access: 'read-only',
+ format: 'r32float',
+ viewDimension: '2d-array',
+ };
+ break;
+ case 'writeonly-storage-texture':
bindGroupLayoutEntry.storageTexture = {
access: 'write-only',
- format: 'rgba8unorm',
+ format: 'r32float',
+ viewDimension: '2d-array',
+ };
+ break;
+ case 'readwrite-storage-texture':
+ bindGroupLayoutEntry.storageTexture = {
+ access: 'read-write',
+ format: 'r32float',
viewDimension: '2d-array',
};
break;
@@ -39,8 +58,8 @@ class F extends ValidationTest {
createBindGroupForTest(
textureView: GPUTextureView,
- textureUsage: 'texture' | 'storage',
- sampleType: 'float' | 'depth' | 'uint',
+ textureUsage: TextureBindingType,
+ sampleType: 'unfilterable-float' | 'depth' | 'uint',
visibility: GPUShaderStage['FRAGMENT'] | GPUShaderStage['COMPUTE'] = GPUShaderStage['FRAGMENT']
) {
return this.device.createBindGroup({
@@ -64,20 +83,16 @@ g.test('subresources,set_bind_group_on_same_index_color_texture')
)
.params(u =>
u
- .combineWithParams([
- { useDifferentTextureAsTexture2: true, baseLayer2: 0, view2Binding: 'texture' },
- { useDifferentTextureAsTexture2: false, baseLayer2: 0, view2Binding: 'texture' },
- { useDifferentTextureAsTexture2: false, baseLayer2: 1, view2Binding: 'texture' },
- { useDifferentTextureAsTexture2: false, baseLayer2: 0, view2Binding: 'storage' },
- { useDifferentTextureAsTexture2: false, baseLayer2: 1, view2Binding: 'storage' },
- ] as const)
- .combine('hasConflict', [true, false])
+ .combine('useDifferentTextureAsTexture2', [true, false])
+ .combine('baseLayer2', [0, 1] as const)
+ .combine('view1Binding', kTextureBindingTypes)
+ .combine('view2Binding', kTextureBindingTypes)
)
.fn(t => {
- const { useDifferentTextureAsTexture2, baseLayer2, view2Binding, hasConflict } = t.params;
+ const { useDifferentTextureAsTexture2, baseLayer2, view1Binding, view2Binding } = t.params;
const texture0 = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
size: [kTextureSize, kTextureSize, kTextureLayers],
});
@@ -87,19 +102,12 @@ g.test('subresources,set_bind_group_on_same_index_color_texture')
baseArrayLayer: 0,
arrayLayerCount: 1,
});
- const bindGroup0 = t.createBindGroupForTest(textureView0, view2Binding, 'float');
-
- // In one renderPassEncoder it is an error to set both bindGroup0 and bindGroup1.
- const view1Binding = hasConflict
- ? view2Binding === 'texture'
- ? 'storage'
- : 'texture'
- : view2Binding;
- const bindGroup1 = t.createBindGroupForTest(textureView0, view1Binding, 'float');
+ const bindGroup0 = t.createBindGroupForTest(textureView0, view1Binding, 'unfilterable-float');
+ const bindGroup1 = t.createBindGroupForTest(textureView0, view2Binding, 'unfilterable-float');
const texture2 = useDifferentTextureAsTexture2
? t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
size: [kTextureSize, kTextureSize, kTextureLayers],
})
@@ -110,10 +118,14 @@ g.test('subresources,set_bind_group_on_same_index_color_texture')
arrayLayerCount: kTextureLayers - baseLayer2,
});
// There should be no conflict between bindGroup0 and validBindGroup2.
- const validBindGroup2 = t.createBindGroupForTest(textureView2, view2Binding, 'float');
+ const validBindGroup2 = t.createBindGroupForTest(
+ textureView2,
+ view2Binding,
+ 'unfilterable-float'
+ );
- const colorTexture = t.device.createTexture({
- format: 'rgba8unorm',
+ const unusedColorTexture = t.device.createTexture({
+ format: 'r32float',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
size: [kTextureSize, kTextureSize, 1],
});
@@ -121,7 +133,7 @@ g.test('subresources,set_bind_group_on_same_index_color_texture')
const renderPassEncoder = encoder.beginRenderPass({
colorAttachments: [
{
- view: colorTexture.createView(),
+ view: unusedColorTexture.createView(),
loadOp: 'load',
storeOp: 'store',
},
@@ -132,9 +144,12 @@ g.test('subresources,set_bind_group_on_same_index_color_texture')
renderPassEncoder.setBindGroup(1, validBindGroup2);
renderPassEncoder.end();
+ const noConflict =
+ (IsReadOnlyTextureBindingType(view1Binding) && IsReadOnlyTextureBindingType(view2Binding)) ||
+ view1Binding === view2Binding;
t.expectValidationError(() => {
encoder.finish();
- }, hasConflict);
+ }, !noConflict);
});
g.test('subresources,set_bind_group_on_same_index_depth_stencil_texture')
@@ -155,6 +170,9 @@ g.test('subresources,set_bind_group_on_same_index_depth_stencil_texture')
format: 'depth24plus-stencil8',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
size: [kTextureSize, kTextureSize, 1],
+ ...(t.isCompatibility && {
+ textureBindingViewDimension: '2d-array',
+ }),
});
const conflictedToNonReadOnlyAttachmentBindGroup = t.createBindGroupForTest(
@@ -162,21 +180,24 @@ g.test('subresources,set_bind_group_on_same_index_depth_stencil_texture')
dimension: '2d-array',
aspect: bindAspect,
}),
- 'texture',
+ 'sampled-texture',
bindAspect === 'depth-only' ? 'depth' : 'uint'
);
const colorTexture = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
size: [kTextureSize, kTextureSize, 1],
+ ...(t.isCompatibility && {
+ textureBindingViewDimension: '2d-array',
+ }),
});
const validBindGroup = t.createBindGroupForTest(
colorTexture.createView({
dimension: '2d-array',
}),
- 'texture',
- 'float'
+ 'sampled-texture',
+ 'unfilterable-float'
);
const encoder = t.device.createCommandEncoder();
@@ -204,12 +225,24 @@ g.test('subresources,set_unused_bind_group')
used in the same render or compute pass encoder, its list of internal usages within one usage
scope can only be a compatible usage list.`
)
- .params(u => u.combine('inRenderPass', [true, false]).combine('hasConflict', [true, false]))
+ .params(u =>
+ u
+ .combine('inRenderPass', [true, false])
+ .combine('textureUsage0', kTextureBindingTypes)
+ .combine('textureUsage1', kTextureBindingTypes)
+ )
.fn(t => {
- const { inRenderPass, hasConflict } = t.params;
+ const { inRenderPass, textureUsage0, textureUsage1 } = t.params;
+
+ if (
+ textureUsage0 === 'readwrite-storage-texture' ||
+ textureUsage1 === 'readwrite-storage-texture'
+ ) {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+ }
const texture0 = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
size: [kTextureSize, kTextureSize, kTextureLayers],
});
@@ -221,40 +254,85 @@ g.test('subresources,set_unused_bind_group')
});
const visibility = inRenderPass ? GPUShaderStage.FRAGMENT : GPUShaderStage.COMPUTE;
// bindGroup0 is used by the pipelines, and bindGroup1 is not used by the pipelines.
- const textureUsage0 = inRenderPass ? 'texture' : 'storage';
- const textureUsage1 = hasConflict ? (inRenderPass ? 'storage' : 'texture') : textureUsage0;
- const bindGroup0 = t.createBindGroupForTest(textureView0, textureUsage0, 'float', visibility);
- const bindGroup1 = t.createBindGroupForTest(textureView0, textureUsage1, 'float', visibility);
+ const bindGroup0 = t.createBindGroupForTest(
+ textureView0,
+ textureUsage0,
+ 'unfilterable-float',
+ visibility
+ );
+ const bindGroup1 = t.createBindGroupForTest(
+ textureView0,
+ textureUsage1,
+ 'unfilterable-float',
+ visibility
+ );
const encoder = t.device.createCommandEncoder();
const colorTexture = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
size: [kTextureSize, kTextureSize, 1],
});
- const pipelineLayout = t.device.createPipelineLayout({
- bindGroupLayouts: [t.createBindGroupLayoutForTest(textureUsage0, 'float', visibility)],
- });
if (inRenderPass) {
+ let fragmentShader = '';
+ switch (textureUsage0) {
+ case 'sampled-texture':
+ fragmentShader = `
+ @group(0) @binding(0) var texture0 : texture_2d_array<f32>;
+ @fragment fn main()
+ -> @location(0) vec4<f32> {
+ return textureLoad(texture0, vec2<i32>(), 0, 0);
+ }
+ `;
+ break;
+ case `readonly-storage-texture`:
+ fragmentShader = `
+ @group(0) @binding(0) var texture0 : texture_storage_2d_array<r32float, read>;
+ @fragment fn main()
+ -> @location(0) vec4<f32> {
+ return textureLoad(texture0, vec2<i32>(), 0);
+ }
+ `;
+ break;
+ case `writeonly-storage-texture`:
+ fragmentShader = `
+ @group(0) @binding(0) var texture0 : texture_storage_2d_array<r32float, write>;
+ @fragment fn main()
+ -> @location(0) vec4<f32> {
+ textureStore(texture0, vec2i(), 0, vec4f(1, 0, 0, 1));
+ return vec4f(0, 0, 0, 1);
+ }
+ `;
+ break;
+ case `readwrite-storage-texture`:
+ fragmentShader = `
+ @group(0) @binding(0) var texture0 : texture_storage_2d_array<r32float, read_write>;
+ @fragment fn main()
+ -> @location(0) vec4<f32> {
+ let color = textureLoad(texture0, vec2i(), 0);
+ textureStore(texture0, vec2i(), 0, vec4f(1, 0, 0, 1));
+ return color;
+ }
+ `;
+ break;
+ }
+
const renderPipeline = t.device.createRenderPipeline({
- layout: pipelineLayout,
+ layout: t.device.createPipelineLayout({
+ bindGroupLayouts: [
+ t.createBindGroupLayoutForTest(textureUsage0, 'unfilterable-float', visibility),
+ ],
+ }),
vertex: {
module: t.device.createShaderModule({
code: t.getNoOpShaderCode('VERTEX'),
}),
- entryPoint: 'main',
},
fragment: {
module: t.device.createShaderModule({
- code: `
- @group(0) @binding(0) var texture0 : texture_2d_array<f32>;
- @fragment fn main()
- -> @location(0) vec4<f32> {
- return textureLoad(texture0, vec2<i32>(), 0, 0);
- }`,
+ code: fragmentShader,
}),
- entryPoint: 'main',
- targets: [{ format: 'rgba8unorm' }],
+ targets: [{ format: 'r32float' }],
},
});
@@ -273,29 +351,97 @@ g.test('subresources,set_unused_bind_group')
renderPassEncoder.draw(1);
renderPassEncoder.end();
} else {
+ let computeShader = '';
+ switch (textureUsage0) {
+ case 'sampled-texture':
+ computeShader = `
+ @group(0) @binding(0) var texture0 : texture_2d_array<f32>;
+ @group(1) @binding(0) var writableStorage : texture_storage_2d_array<r32float, write>;
+ @compute @workgroup_size(1) fn main() {
+ let value = textureLoad(texture0, vec2i(), 0, 0);
+ textureStore(writableStorage, vec2i(), 0, value);
+ }
+ `;
+ break;
+ case `readonly-storage-texture`:
+ computeShader = `
+ @group(0) @binding(0) var texture0 : texture_storage_2d_array<r32float, read>;
+ @group(1) @binding(0) var writableStorage : texture_storage_2d_array<r32float, write>;
+ @compute @workgroup_size(1) fn main() {
+ let value = textureLoad(texture0, vec2<i32>(), 0);
+ textureStore(writableStorage, vec2i(), 0, value);
+ }
+ `;
+ break;
+ case `writeonly-storage-texture`:
+ computeShader = `
+ @group(0) @binding(0) var texture0 : texture_storage_2d_array<r32float, write>;
+ @group(1) @binding(0) var writableStorage : texture_storage_2d_array<r32float, write>;
+ @compute @workgroup_size(1) fn main() {
+ textureStore(texture0, vec2i(), 0, vec4f(1, 0, 0, 1));
+ textureStore(writableStorage, vec2i(), 0, vec4f(1, 0, 0, 1));
+ }
+ `;
+ break;
+ case `readwrite-storage-texture`:
+ computeShader = `
+ @group(0) @binding(0) var texture0 : texture_storage_2d_array<r32float, read_write>;
+ @group(1) @binding(0) var writableStorage : texture_storage_2d_array<r32float, write>;
+ @compute @workgroup_size(1) fn main() {
+ let color = textureLoad(texture0, vec2i(), 0);
+ textureStore(texture0, vec2i(), 0, vec4f(1, 0, 0, 1));
+ textureStore(writableStorage, vec2i(), 0, color);
+ }
+ `;
+ break;
+ }
+
+ const pipelineLayout = t.device.createPipelineLayout({
+ bindGroupLayouts: [
+ t.createBindGroupLayoutForTest(textureUsage0, 'unfilterable-float', visibility),
+ t.createBindGroupLayoutForTest(
+ 'writeonly-storage-texture',
+ 'unfilterable-float',
+ visibility
+ ),
+ ],
+ });
const computePipeline = t.device.createComputePipeline({
layout: pipelineLayout,
compute: {
module: t.device.createShaderModule({
- code: `
- @group(0) @binding(0) var texture0 : texture_storage_2d_array<rgba8unorm, write>;
- @compute @workgroup_size(1)
- fn main() {
- textureStore(texture0, vec2<i32>(), 0, vec4<f32>());
- }`,
+ code: computeShader,
}),
- entryPoint: 'main',
},
});
+
+ const writableStorageTexture = t.device.createTexture({
+ format: 'r32float',
+ usage: GPUTextureUsage.STORAGE_BINDING,
+ size: [kTextureSize, kTextureSize, 1],
+ });
+ const writableStorageTextureView = writableStorageTexture.createView({
+ dimension: '2d-array',
+ baseArrayLayer: 0,
+ arrayLayerCount: 1,
+ });
+ const writableStorageTextureBindGroup = t.createBindGroupForTest(
+ writableStorageTextureView,
+ 'writeonly-storage-texture',
+ 'unfilterable-float',
+ visibility
+ );
+
const computePassEncoder = encoder.beginComputePass();
computePassEncoder.setBindGroup(0, bindGroup0);
- computePassEncoder.setBindGroup(1, bindGroup1);
+ computePassEncoder.setBindGroup(1, writableStorageTextureBindGroup);
+ computePassEncoder.setBindGroup(2, bindGroup1);
computePassEncoder.setPipeline(computePipeline);
computePassEncoder.dispatchWorkgroups(1);
computePassEncoder.end();
}
- // In WebGPU SPEC (Chapter 3.4.5, Synchronization):
+ // In WebGPU SPEC (https://gpuweb.github.io/gpuweb/#programming-model-synchronization):
// This specification defines the following usage scopes:
// - In a compute pass, each dispatch command (dispatchWorkgroups() or
// dispatchWorkgroupsIndirect()) is one usage scope. A subresource is "used" in the usage
@@ -306,7 +452,11 @@ g.test('subresources,set_unused_bind_group')
// referenced by any (state-setting or non-state-setting) command. For example, in
// setBindGroup(index, bindGroup, dynamicOffsets), every subresource in bindGroup is "used" in
// the render pass’s usage scope.
- const success = !inRenderPass || !hasConflict;
+ const success =
+ !inRenderPass ||
+ (IsReadOnlyTextureBindingType(textureUsage0) &&
+ IsReadOnlyTextureBindingType(textureUsage1)) ||
+ textureUsage0 === textureUsage1;
t.expectValidationError(() => {
encoder.finish();
}, !success);
@@ -324,16 +474,14 @@ g.test('subresources,texture_usages_in_copy_and_render_pass')
.combine('usage0', [
'copy-src',
'copy-dst',
- 'texture',
- 'storage',
'color-attachment',
+ ...kTextureBindingTypes,
] as const)
.combine('usage1', [
'copy-src',
'copy-dst',
- 'texture',
- 'storage',
'color-attachment',
+ ...kTextureBindingTypes,
] as const)
.filter(
({ usage0, usage1 }) =>
@@ -347,7 +495,7 @@ g.test('subresources,texture_usages_in_copy_and_render_pass')
const { usage0, usage1 } = t.params;
const texture = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage:
GPUTextureUsage.COPY_SRC |
GPUTextureUsage.COPY_DST |
@@ -355,11 +503,14 @@ g.test('subresources,texture_usages_in_copy_and_render_pass')
GPUTextureUsage.STORAGE_BINDING |
GPUTextureUsage.RENDER_ATTACHMENT,
size: [kTextureSize, kTextureSize, 1],
+ ...(t.isCompatibility && {
+ textureBindingViewDimension: '2d-array',
+ }),
});
const UseTextureOnCommandEncoder = (
texture: GPUTexture,
- usage: 'copy-src' | 'copy-dst' | 'texture' | 'storage' | 'color-attachment',
+ usage: 'copy-src' | 'copy-dst' | 'color-attachment' | TextureBindingType,
encoder: GPUCommandEncoder
) => {
switch (usage) {
@@ -386,10 +537,12 @@ g.test('subresources,texture_usages_in_copy_and_render_pass')
renderPassEncoder.end();
break;
}
- case 'texture':
- case 'storage': {
+ case 'sampled-texture':
+ case 'readonly-storage-texture':
+ case 'writeonly-storage-texture':
+ case 'readwrite-storage-texture': {
const colorTexture = t.device.createTexture({
- format: 'rgba8unorm',
+ format: 'r32float',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
size: [kTextureSize, kTextureSize, 1],
});
@@ -403,7 +556,7 @@ g.test('subresources,texture_usages_in_copy_and_render_pass')
dimension: '2d-array',
}),
usage,
- 'float'
+ 'unfilterable-float'
);
renderPassEncoder.setBindGroup(0, bindGroup);
renderPassEncoder.end();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/shader_module/entry_point.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/shader_module/entry_point.spec.ts
index 1a8da470a4..c956dc3021 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/shader_module/entry_point.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/shader_module/entry_point.spec.ts
@@ -3,6 +3,7 @@ This tests entry point validation of compute/render pipelines and their shader m
The entryPoint in shader module include standard "main" and others.
The entryPoint assigned in descriptor include:
+- Undefined with matching entry point for stage
- Matching case (control case)
- Empty string
- Mistyping
@@ -10,7 +11,6 @@ The entryPoint assigned in descriptor include:
- Unicode entrypoints and their ASCIIfied version
TODO:
-- Test unicode normalization (gpuweb/gpuweb#1160)
- Fine-tune test cases to reduce number by removing trivially similar cases
`;
@@ -43,23 +43,52 @@ const kEntryPointTestCases = [
g.test('compute')
.desc(
`
-Tests calling createComputePipeline(Async) with valid vertex stage shader and different entryPoints,
+Tests calling createComputePipeline(Async) with valid compute stage shader and different entryPoints,
and check that the APIs only accept matching entryPoint.
`
)
- .params(u => u.combine('isAsync', [true, false]).combineWithParams(kEntryPointTestCases))
+ .params(u =>
+ u
+ .combine('isAsync', [true, false])
+ .combine('shaderModuleStage', ['compute', 'vertex', 'fragment'] as const)
+ .beginSubcases()
+ .combine('provideEntryPoint', [true, false])
+ .combine('extraEntryPoint', [true, false])
+ .combineWithParams(kEntryPointTestCases)
+ )
.fn(t => {
- const { isAsync, shaderModuleEntryPoint, stageEntryPoint } = t.params;
+ const {
+ isAsync,
+ provideEntryPoint,
+ extraEntryPoint,
+ shaderModuleStage,
+ shaderModuleEntryPoint,
+ stageEntryPoint,
+ } = t.params;
+ const entryPoint = provideEntryPoint ? stageEntryPoint : undefined;
+ let code = getShaderWithEntryPoint(shaderModuleStage, shaderModuleEntryPoint);
+ if (extraEntryPoint) {
+ code += ` ${getShaderWithEntryPoint(shaderModuleStage, 'extra')}`;
+ }
const descriptor: GPUComputePipelineDescriptor = {
layout: 'auto',
compute: {
module: t.device.createShaderModule({
- code: getShaderWithEntryPoint('compute', shaderModuleEntryPoint),
+ code,
}),
- entryPoint: stageEntryPoint,
+ entryPoint,
},
};
- const _success = shaderModuleEntryPoint === stageEntryPoint;
+ let _success = true;
+ if (shaderModuleStage !== 'compute') {
+ _success = false;
+ }
+ if (!provideEntryPoint && extraEntryPoint) {
+ _success = false;
+ }
+ if (shaderModuleEntryPoint !== stageEntryPoint && provideEntryPoint) {
+ _success = false;
+ }
t.doCreateComputePipelineTest(isAsync, _success, descriptor);
});
@@ -70,19 +99,46 @@ Tests calling createRenderPipeline(Async) with valid vertex stage shader and dif
and check that the APIs only accept matching entryPoint.
`
)
- .params(u => u.combine('isAsync', [true, false]).combineWithParams(kEntryPointTestCases))
+ .params(u =>
+ u
+ .combine('isAsync', [true, false])
+ .combine('shaderModuleStage', ['compute', 'vertex', 'fragment'] as const)
+ .beginSubcases()
+ .combine('provideEntryPoint', [true, false])
+ .combine('extraEntryPoint', [true, false])
+ .combineWithParams(kEntryPointTestCases)
+ )
.fn(t => {
- const { isAsync, shaderModuleEntryPoint, stageEntryPoint } = t.params;
+ const {
+ isAsync,
+ provideEntryPoint,
+ extraEntryPoint,
+ shaderModuleStage,
+ shaderModuleEntryPoint,
+ stageEntryPoint,
+ } = t.params;
+ const entryPoint = provideEntryPoint ? stageEntryPoint : undefined;
+ let code = getShaderWithEntryPoint(shaderModuleStage, shaderModuleEntryPoint);
+ if (extraEntryPoint) {
+ code += ` ${getShaderWithEntryPoint(shaderModuleStage, 'extra')}`;
+ }
const descriptor: GPURenderPipelineDescriptor = {
layout: 'auto',
vertex: {
- module: t.device.createShaderModule({
- code: getShaderWithEntryPoint('vertex', shaderModuleEntryPoint),
- }),
- entryPoint: stageEntryPoint,
+ module: t.device.createShaderModule({ code }),
+ entryPoint,
},
};
- const _success = shaderModuleEntryPoint === stageEntryPoint;
+ let _success = true;
+ if (shaderModuleStage !== 'vertex') {
+ _success = false;
+ }
+ if (!provideEntryPoint && extraEntryPoint) {
+ _success = false;
+ }
+ if (shaderModuleEntryPoint !== stageEntryPoint && provideEntryPoint) {
+ _success = false;
+ }
t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
});
@@ -93,25 +149,155 @@ Tests calling createRenderPipeline(Async) with valid fragment stage shader and d
and check that the APIs only accept matching entryPoint.
`
)
- .params(u => u.combine('isAsync', [true, false]).combineWithParams(kEntryPointTestCases))
+ .params(u =>
+ u
+ .combine('isAsync', [true, false])
+ .combine('shaderModuleStage', ['compute', 'vertex', 'fragment'] as const)
+ .beginSubcases()
+ .combine('provideEntryPoint', [true, false])
+ .combine('extraEntryPoint', [true, false])
+ .combineWithParams(kEntryPointTestCases)
+ )
.fn(t => {
- const { isAsync, shaderModuleEntryPoint, stageEntryPoint } = t.params;
+ const {
+ isAsync,
+ provideEntryPoint,
+ extraEntryPoint,
+ shaderModuleStage,
+ shaderModuleEntryPoint,
+ stageEntryPoint,
+ } = t.params;
+ const entryPoint = provideEntryPoint ? stageEntryPoint : undefined;
+ let code = getShaderWithEntryPoint(shaderModuleStage, shaderModuleEntryPoint);
+ if (extraEntryPoint) {
+ code += ` ${getShaderWithEntryPoint(shaderModuleStage, 'extra')}`;
+ }
const descriptor: GPURenderPipelineDescriptor = {
layout: 'auto',
vertex: {
module: t.device.createShaderModule({
code: kDefaultVertexShaderCode,
}),
- entryPoint: 'main',
},
fragment: {
module: t.device.createShaderModule({
- code: getShaderWithEntryPoint('fragment', shaderModuleEntryPoint),
+ code,
}),
- entryPoint: stageEntryPoint,
+ entryPoint,
targets: [{ format: 'rgba8unorm' }],
},
};
- const _success = shaderModuleEntryPoint === stageEntryPoint;
+ let _success = true;
+ if (shaderModuleStage !== 'fragment') {
+ _success = false;
+ }
+ if (!provideEntryPoint && extraEntryPoint) {
+ _success = false;
+ }
+ if (shaderModuleEntryPoint !== stageEntryPoint && provideEntryPoint) {
+ _success = false;
+ }
t.doCreateRenderPipelineTest(isAsync, _success, descriptor);
});
+
+g.test('compute_undefined_entry_point_and_extra_stage')
+ .desc(
+ `
+Tests calling createComputePipeline(Async) with compute stage shader and
+an undefined entryPoint is valid if there's an extra shader stage.
+`
+ )
+ .params(u =>
+ u
+ .combine('isAsync', [true, false])
+ .combine('extraShaderModuleStage', ['compute', 'vertex', 'fragment'] as const)
+ )
+ .fn(t => {
+ const { isAsync, extraShaderModuleStage } = t.params;
+ const code = `
+ ${getShaderWithEntryPoint('compute', 'main')}
+ ${getShaderWithEntryPoint(extraShaderModuleStage, 'extra')}
+ `;
+ const descriptor: GPUComputePipelineDescriptor = {
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code,
+ }),
+ entryPoint: undefined,
+ },
+ };
+
+ const success = extraShaderModuleStage !== 'compute';
+ t.doCreateComputePipelineTest(isAsync, success, descriptor);
+ });
+
+g.test('vertex_undefined_entry_point_and_extra_stage')
+ .desc(
+ `
+Tests calling createRenderPipeline(Async) with vertex stage shader and
+an undefined entryPoint is valid if there's an extra shader stage.
+`
+ )
+ .params(u =>
+ u
+ .combine('isAsync', [true, false])
+ .combine('extraShaderModuleStage', ['compute', 'vertex', 'fragment'] as const)
+ )
+ .fn(t => {
+ const { isAsync, extraShaderModuleStage } = t.params;
+ const code = `
+ ${getShaderWithEntryPoint('vertex', 'main')}
+ ${getShaderWithEntryPoint(extraShaderModuleStage, 'extra')}
+ `;
+ const descriptor: GPURenderPipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code,
+ }),
+ entryPoint: undefined,
+ },
+ };
+
+ const success = extraShaderModuleStage !== 'vertex';
+ t.doCreateRenderPipelineTest(isAsync, success, descriptor);
+ });
+
+g.test('fragment_undefined_entry_point_and_extra_stage')
+ .desc(
+ `
+Tests calling createRenderPipeline(Async) with fragment stage shader and
+an undefined entryPoint is valid if there's an extra shader stage.
+`
+ )
+ .params(u =>
+ u
+ .combine('isAsync', [true, false])
+ .combine('extraShaderModuleStage', ['compute', 'vertex', 'fragment'] as const)
+ )
+ .fn(t => {
+ const { isAsync, extraShaderModuleStage } = t.params;
+ const code = `
+ ${getShaderWithEntryPoint('fragment', 'main')}
+ ${getShaderWithEntryPoint(extraShaderModuleStage, 'extra')}
+ `;
+ const descriptor: GPURenderPipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({
+ code: kDefaultVertexShaderCode,
+ }),
+ },
+ fragment: {
+ module: t.device.createShaderModule({
+ code,
+ }),
+ entryPoint: undefined,
+ targets: [{ format: 'rgba8unorm' }],
+ },
+ };
+
+ const success = extraShaderModuleStage !== 'fragment';
+ t.doCreateRenderPipelineTest(isAsync, success, descriptor);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/state/device_lost/destroy.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/state/device_lost/destroy.spec.ts
index df03427a0a..42f069ee77 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/state/device_lost/destroy.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/state/device_lost/destroy.spec.ts
@@ -704,10 +704,7 @@ Tests import external texture on destroyed device. Tests valid combinations of:
entries: [
{
binding: 0,
- resource: t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
- }),
+ resource: t.device.importExternalTexture({ source }),
},
],
});
@@ -899,7 +896,12 @@ Tests encoding and finishing a writeTimestamp command on destroyed device.
const { type, stage, awaitLost } = t.params;
const querySet = t.device.createQuerySet({ type, count: 2 });
await t.executeCommandsAfterDestroy(stage, awaitLost, 'non-pass', maker => {
- maker.encoder.writeTimestamp(querySet, 0);
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (maker.encoder as any).writeTimestamp(querySet, 0);
+ } catch (ex) {
+ t.skipIf(ex instanceof TypeError, 'writeTimestamp is actually not available');
+ }
return maker;
});
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/texture/bgra8unorm_storage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/texture/bgra8unorm_storage.spec.ts
index 80872fd5d3..0ae600eb49 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/texture/bgra8unorm_storage.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/texture/bgra8unorm_storage.spec.ts
@@ -81,37 +81,6 @@ validation cases where this feature is not enabled, which are skipped here.
});
});
-g.test('create_shader_module_with_bgra8unorm_storage')
- .desc(
- `
-Test that it is valid to declare the format of a storage texture as bgra8unorm in a shader module if
-the feature bgra8unorm-storage is enabled.
-`
- )
- .beforeAllSubcases(t => {
- t.selectDeviceOrSkipTestCase('bgra8unorm-storage');
- })
- .params(u => u.combine('shaderType', ['fragment', 'compute'] as const))
- .fn(t => {
- const { shaderType } = t.params;
-
- t.testCreateShaderModuleWithBGRA8UnormStorage(shaderType, true);
- });
-
-g.test('create_shader_module_without_bgra8unorm_storage')
- .desc(
- `
-Test that it is invalid to declare the format of a storage texture as bgra8unorm in a shader module
-if the feature bgra8unorm-storage is not enabled.
-`
- )
- .params(u => u.combine('shaderType', ['fragment', 'compute'] as const))
- .fn(t => {
- const { shaderType } = t.params;
-
- t.testCreateShaderModuleWithBGRA8UnormStorage(shaderType, false);
- });
-
g.test('configure_storage_usage_on_canvas_context_without_bgra8unorm_storage')
.desc(
`
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/utils.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/utils.ts
new file mode 100644
index 0000000000..e6ee87f797
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/utils.ts
@@ -0,0 +1,275 @@
+interface Resource {
+ readonly buffer?: GPUBufferBindingLayout;
+ readonly sampler?: GPUSamplerBindingLayout;
+ readonly texture?: GPUTextureBindingLayout;
+ readonly storageTexture?: GPUStorageTextureBindingLayout;
+ readonly externalTexture?: GPUExternalTextureBindingLayout;
+ readonly code: string;
+ readonly staticUse?: string;
+}
+
+/**
+ * Returns an array of possible resources
+ */
+function generateResources(): Resource[] {
+ const resources: Resource[] = [
+ // Buffers
+ {
+ buffer: { type: 'uniform' },
+ code: `var<uniform> res : array<vec4u, 16>`,
+ staticUse: `res[0]`,
+ },
+ {
+ buffer: { type: 'storage' },
+ code: `var<storage, read_write> res : array<vec4u>`,
+ staticUse: `res[0]`,
+ },
+ {
+ buffer: { type: 'read-only-storage' },
+ code: `var<storage> res : array<vec4u>`,
+ staticUse: `res[0]`,
+ },
+
+ // Samplers
+ {
+ sampler: { type: 'filtering' },
+ code: `var res : sampler`,
+ },
+ {
+ sampler: { type: 'non-filtering' },
+ code: `var res : sampler`,
+ },
+ {
+ sampler: { type: 'comparison' },
+ code: `var res : sampler_comparison`,
+ },
+ // Multisampled textures
+ {
+ texture: { sampleType: 'depth', viewDimension: '2d', multisampled: true },
+ code: `var res : texture_depth_multisampled_2d`,
+ },
+ {
+ texture: { sampleType: 'unfilterable-float', viewDimension: '2d', multisampled: true },
+ code: `var res : texture_multisampled_2d<f32>`,
+ },
+ {
+ texture: { sampleType: 'sint', viewDimension: '2d', multisampled: true },
+ code: `var res : texture_multisampled_2d<i32>`,
+ },
+ {
+ texture: { sampleType: 'uint', viewDimension: '2d', multisampled: true },
+ code: `var res : texture_multisampled_2d<u32>`,
+ },
+ ];
+
+ // Sampled textures
+ const sampleDims: GPUTextureViewDimension[] = [
+ '1d',
+ '2d',
+ '2d-array',
+ '3d',
+ 'cube',
+ 'cube-array',
+ ];
+ const sampleTypes: GPUTextureSampleType[] = ['float', 'unfilterable-float', 'sint', 'uint'];
+ const sampleWGSL = ['f32', 'f32', 'i32', 'u32'];
+ for (const dim of sampleDims) {
+ let i = 0;
+ for (const type of sampleTypes) {
+ resources.push({
+ texture: { sampleType: type, viewDimension: dim, multisampled: false },
+ code: `var res : texture_${dim.replace('-', '_')}<${sampleWGSL[i++]}>`,
+ });
+ }
+ }
+
+ // Depth textures
+ const depthDims: GPUTextureViewDimension[] = ['2d', '2d-array', 'cube', 'cube-array'];
+ for (const dim of depthDims) {
+ resources.push({
+ texture: { sampleType: 'depth', viewDimension: dim, multisampled: false },
+ code: `var res : texture_depth_${dim.replace('-', '_')}`,
+ });
+ }
+
+ // Storage textures
+ // Only cover r32uint, r32sint, and r32float here for ease of testing.
+ const storageDims: GPUTextureViewDimension[] = ['1d', '2d', '2d-array', '3d'];
+ const formats: GPUTextureFormat[] = ['r32float', 'r32sint', 'r32uint'];
+ const accesses: GPUStorageTextureAccess[] = ['write-only', 'read-only', 'read-write'];
+ for (const dim of storageDims) {
+ for (const format of formats) {
+ for (const access of accesses) {
+ resources.push({
+ storageTexture: { access, format, viewDimension: dim },
+ code: `var res : texture_storage_${dim.replace('-', '_')}<${format},${access
+ .replace('-only', '')
+ .replace('-', '_')}>`,
+ });
+ }
+ }
+ }
+
+ return resources;
+}
+
+/**
+ * Returns a string suitable as a Record key.
+ */
+function resourceKey(res: Resource): string {
+ if (res.buffer) {
+ return `${res.buffer.type}_buffer`;
+ }
+ if (res.sampler) {
+ return `${res.sampler.type}_sampler`;
+ }
+ if (res.texture) {
+ return `texture_${res.texture.sampleType}_${res.texture.viewDimension}_${res.texture.multisampled}`;
+ }
+ if (res.storageTexture) {
+ return `storage_texture_${res.storageTexture.viewDimension}_${res.storageTexture.format}_${res.storageTexture.access}`;
+ }
+ if (res.externalTexture) {
+ return `external_texture`;
+ }
+ return ``;
+}
+
+/**
+ * Resource array converted to a Record for nicer test parameterization names.
+ */
+export const kAPIResources: Record<string, Resource> = Object.fromEntries(
+ generateResources().map(x => [resourceKey(x), x])
+) as Record<string, Resource>;
+
+/**
+ * Generates a shader of the specified stage using the specified resource at binding (0,0).
+ */
+export function getWGSLShaderForResource(stage: string, resource: Resource): string {
+ let code = `@group(0) @binding(0) ${resource.code};\n`;
+
+ code += `@${stage}`;
+ if (stage === 'compute') {
+ code += `@workgroup_size(1)`;
+ }
+
+ let retTy = '';
+ let retVal = '';
+ if (stage === 'vertex') {
+ retTy = ' -> @builtin(position) vec4f';
+ retVal = 'return vec4f();';
+ } else if (stage === 'fragment') {
+ retTy = ' -> @location(0) vec4f';
+ retVal = 'return vec4f();';
+ }
+ code += `
+fn main() ${retTy} {
+ _ = ${resource.staticUse ?? 'res'};
+ ${retVal}
+}
+`;
+
+ return code;
+}
+
+/**
+ * Generates a bind group layout for for the given resource at binding 0.
+ */
+export function getAPIBindGroupLayoutForResource(
+ device: GPUDevice,
+ stage: GPUShaderStageFlags,
+ resource: Resource
+): GPUBindGroupLayout {
+ const entry: GPUBindGroupLayoutEntry = {
+ binding: 0,
+ visibility: stage,
+ };
+ if (resource.buffer) {
+ entry.buffer = resource.buffer;
+ }
+ if (resource.sampler) {
+ entry.sampler = resource.sampler;
+ }
+ if (resource.texture) {
+ entry.texture = resource.texture;
+ }
+ if (resource.storageTexture) {
+ entry.storageTexture = resource.storageTexture;
+ }
+ if (resource.externalTexture) {
+ entry.externalTexture = resource.externalTexture;
+ }
+
+ const entries: GPUBindGroupLayoutEntry[] = [entry];
+ return device.createBindGroupLayout({ entries });
+}
+
+/**
+ * Returns true if the sample types are compatible.
+ */
+function doSampleTypesMatch(api: GPUTextureSampleType, wgsl: GPUTextureSampleType): boolean {
+ if (api === 'float' || api === 'unfilterable-float') {
+ return wgsl === 'float' || wgsl === 'unfilterable-float';
+ }
+ return api === wgsl;
+}
+
+/**
+ * Returns true if the access modes are compatible.
+ */
+function doAccessesMatch(api: GPUStorageTextureAccess, wgsl: GPUStorageTextureAccess): boolean {
+ if (api === 'read-write') {
+ return wgsl === 'read-write' || wgsl === 'write-only';
+ }
+ return api === wgsl;
+}
+
+/**
+ * Returns true if the resources are compatible.
+ */
+export function doResourcesMatch(api: Resource, wgsl: Resource): boolean {
+ if (api.buffer) {
+ if (!wgsl.buffer) {
+ return false;
+ }
+ return api.buffer.type === wgsl.buffer.type;
+ }
+ if (api.sampler) {
+ if (!wgsl.sampler) {
+ return false;
+ }
+ return (
+ api.sampler.type === wgsl.sampler.type ||
+ (api.sampler.type !== 'comparison' && wgsl.sampler.type !== 'comparison')
+ );
+ }
+ if (api.texture) {
+ if (!wgsl.texture) {
+ return false;
+ }
+ const aType = api.texture.sampleType as GPUTextureSampleType;
+ const wType = wgsl.texture.sampleType as GPUTextureSampleType;
+ return (
+ doSampleTypesMatch(aType, wType) &&
+ api.texture.viewDimension === wgsl.texture.viewDimension &&
+ api.texture.multisampled === wgsl.texture.multisampled
+ );
+ }
+ if (api.storageTexture) {
+ if (!wgsl.storageTexture) {
+ return false;
+ }
+ const aAccess = api.storageTexture.access as GPUStorageTextureAccess;
+ const wAccess = wgsl.storageTexture.access as GPUStorageTextureAccess;
+ return (
+ doAccessesMatch(aAccess, wAccess) &&
+ api.storageTexture.format === wgsl.storageTexture.format &&
+ api.storageTexture.viewDimension === wgsl.storageTexture.viewDimension
+ );
+ }
+ if (api.externalTexture) {
+ return wgsl.externalTexture !== undefined;
+ }
+
+ return false;
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/validation_test.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/validation_test.ts
index 7ee5b9f7c1..6e6802d95f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/validation_test.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/validation_test.ts
@@ -152,11 +152,11 @@ export class ValidationTest extends GPUTest {
}
/** Return an arbitrarily-configured GPUTexture with the `STORAGE_BINDING` usage. */
- getStorageTexture(): GPUTexture {
+ getStorageTexture(format: GPUTextureFormat): GPUTexture {
return this.trackForCleanup(
this.device.createTexture({
size: { width: 16, height: 16, depthOrArrayLayers: 1 },
- format: 'rgba8unorm',
+ format,
usage: GPUTextureUsage.STORAGE_BINDING,
})
);
@@ -220,8 +220,10 @@ export class ValidationTest extends GPUTest {
return this.getSampledTexture(1).createView();
case 'sampledTexMS':
return this.getSampledTexture(4).createView();
- case 'storageTex':
- return this.getStorageTexture().createView();
+ case 'readonlyStorageTex':
+ case 'writeonlyStorageTex':
+ case 'readwriteStorageTex':
+ return this.getStorageTexture('r32float').createView();
}
}
@@ -255,10 +257,10 @@ export class ValidationTest extends GPUTest {
}
/** Return an arbitrarily-configured GPUTexture with the `STORAGE` usage from mismatched device. */
- getDeviceMismatchedStorageTexture(): GPUTexture {
+ getDeviceMismatchedStorageTexture(format: GPUTextureFormat): GPUTexture {
return this.getDeviceMismatchedTexture({
size: { width: 4, height: 4, depthOrArrayLayers: 1 },
- format: 'rgba8unorm',
+ format,
usage: GPUTextureUsage.STORAGE_BINDING,
});
}
@@ -289,8 +291,10 @@ export class ValidationTest extends GPUTest {
return this.getDeviceMismatchedSampledTexture(1).createView();
case 'sampledTexMS':
return this.getDeviceMismatchedSampledTexture(4).createView();
- case 'storageTex':
- return this.getDeviceMismatchedStorageTexture().createView();
+ case 'readonlyStorageTex':
+ case 'writeonlyStorageTex':
+ case 'readwriteStorageTex':
+ return this.getDeviceMismatchedStorageTexture('r32float').createView();
}
}
@@ -317,7 +321,8 @@ export class ValidationTest extends GPUTest {
/** Return a GPURenderPipeline with default options and no-op vertex and fragment shaders. */
createNoOpRenderPipeline(
- layout: GPUPipelineLayout | GPUAutoLayoutMode = 'auto'
+ layout: GPUPipelineLayout | GPUAutoLayoutMode = 'auto',
+ colorFormat: GPUTextureFormat = 'rgba8unorm'
): GPURenderPipeline {
return this.device.createRenderPipeline({
layout,
@@ -332,7 +337,7 @@ export class ValidationTest extends GPUTest {
code: this.getNoOpShaderCode('FRAGMENT'),
}),
entryPoint: 'main',
- targets: [{ format: 'rgba8unorm', writeMask: 0 }],
+ targets: [{ format: colorFormat, writeMask: 0 }],
},
primitive: { topology: 'triangle-list' },
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/capability_info.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/capability_info.ts
index d65313c006..d26d93ca2e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/capability_info.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/capability_info.ts
@@ -322,7 +322,9 @@ export type PerStageBindingLimitClass =
| 'storageBuf'
| 'sampler'
| 'sampledTex'
- | 'storageTex';
+ | 'readonlyStorageTex'
+ | 'writeonlyStorageTex'
+ | 'readwriteStorageTex';
/**
* Classes of `PerPipelineLayout` binding limits. Two bindings with the same class
* count toward the same `PerPipelineLayout` limit(s) in the spec (if any).
@@ -337,7 +339,9 @@ export type ValidBindableResource =
| 'compareSamp'
| 'sampledTex'
| 'sampledTexMS'
- | 'storageTex';
+ | 'readonlyStorageTex'
+ | 'writeonlyStorageTex'
+ | 'readwriteStorageTex';
type ErrorBindableResource = 'errorBuf' | 'errorSamp' | 'errorTex';
/**
@@ -353,7 +357,9 @@ export const kBindableResources = [
'compareSamp',
'sampledTex',
'sampledTexMS',
- 'storageTex',
+ 'readonlyStorageTex',
+ 'writeonlyStorageTex',
+ 'readwriteStorageTex',
'errorBuf',
'errorSamp',
'errorTex',
@@ -376,11 +382,13 @@ export const kPerStageBindingLimits: {
};
} =
/* prettier-ignore */ {
- 'uniformBuf': { class: 'uniformBuf', maxLimit: 'maxUniformBuffersPerShaderStage', },
- 'storageBuf': { class: 'storageBuf', maxLimit: 'maxStorageBuffersPerShaderStage', },
- 'sampler': { class: 'sampler', maxLimit: 'maxSamplersPerShaderStage', },
- 'sampledTex': { class: 'sampledTex', maxLimit: 'maxSampledTexturesPerShaderStage', },
- 'storageTex': { class: 'storageTex', maxLimit: 'maxStorageTexturesPerShaderStage', },
+ 'uniformBuf': { class: 'uniformBuf', maxLimit: 'maxUniformBuffersPerShaderStage', },
+ 'storageBuf': { class: 'storageBuf', maxLimit: 'maxStorageBuffersPerShaderStage', },
+ 'sampler': { class: 'sampler', maxLimit: 'maxSamplersPerShaderStage', },
+ 'sampledTex': { class: 'sampledTex', maxLimit: 'maxSampledTexturesPerShaderStage', },
+ 'readonlyStorageTex': { class: 'readonlyStorageTex', maxLimit: 'maxStorageTexturesPerShaderStage', },
+ 'writeonlyStorageTex': { class: 'writeonlyStorageTex', maxLimit: 'maxStorageTexturesPerShaderStage', },
+ 'readwriteStorageTex': { class: 'readwriteStorageTex', maxLimit: 'maxStorageTexturesPerShaderStage', },
};
/**
@@ -398,11 +406,13 @@ export const kPerPipelineBindingLimits: {
};
} =
/* prettier-ignore */ {
- 'uniformBuf': { class: 'uniformBuf', maxDynamicLimit: 'maxDynamicUniformBuffersPerPipelineLayout', },
- 'storageBuf': { class: 'storageBuf', maxDynamicLimit: 'maxDynamicStorageBuffersPerPipelineLayout', },
- 'sampler': { class: 'sampler', maxDynamicLimit: '', },
- 'sampledTex': { class: 'sampledTex', maxDynamicLimit: '', },
- 'storageTex': { class: 'storageTex', maxDynamicLimit: '', },
+ 'uniformBuf': { class: 'uniformBuf', maxDynamicLimit: 'maxDynamicUniformBuffersPerPipelineLayout', },
+ 'storageBuf': { class: 'storageBuf', maxDynamicLimit: 'maxDynamicStorageBuffersPerPipelineLayout', },
+ 'sampler': { class: 'sampler', maxDynamicLimit: '', },
+ 'sampledTex': { class: 'sampledTex', maxDynamicLimit: '', },
+ 'readonlyStorageTex': { class: 'readonlyStorageTex', maxDynamicLimit: '', },
+ 'writeonlyStorageTex': { class: 'writeonlyStorageTex', maxDynamicLimit: '', },
+ 'readwriteStorageTex': { class: 'readwriteStorageTex', maxDynamicLimit: '', },
};
interface BindingKindInfo {
@@ -416,14 +426,16 @@ const kBindingKind: {
readonly [k in ValidBindableResource]: BindingKindInfo;
} =
/* prettier-ignore */ {
- uniformBuf: { resource: 'uniformBuf', perStageLimitClass: kPerStageBindingLimits.uniformBuf, perPipelineLimitClass: kPerPipelineBindingLimits.uniformBuf, },
- storageBuf: { resource: 'storageBuf', perStageLimitClass: kPerStageBindingLimits.storageBuf, perPipelineLimitClass: kPerPipelineBindingLimits.storageBuf, },
- filtSamp: { resource: 'filtSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, },
- nonFiltSamp: { resource: 'nonFiltSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, },
- compareSamp: { resource: 'compareSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, },
- sampledTex: { resource: 'sampledTex', perStageLimitClass: kPerStageBindingLimits.sampledTex, perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex, },
- sampledTexMS: { resource: 'sampledTexMS', perStageLimitClass: kPerStageBindingLimits.sampledTex, perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex, },
- storageTex: { resource: 'storageTex', perStageLimitClass: kPerStageBindingLimits.storageTex, perPipelineLimitClass: kPerPipelineBindingLimits.storageTex, },
+ uniformBuf: { resource: 'uniformBuf', perStageLimitClass: kPerStageBindingLimits.uniformBuf, perPipelineLimitClass: kPerPipelineBindingLimits.uniformBuf, },
+ storageBuf: { resource: 'storageBuf', perStageLimitClass: kPerStageBindingLimits.storageBuf, perPipelineLimitClass: kPerPipelineBindingLimits.storageBuf, },
+ filtSamp: { resource: 'filtSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, },
+ nonFiltSamp: { resource: 'nonFiltSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, },
+ compareSamp: { resource: 'compareSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, },
+ sampledTex: { resource: 'sampledTex', perStageLimitClass: kPerStageBindingLimits.sampledTex, perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex, },
+ sampledTexMS: { resource: 'sampledTexMS', perStageLimitClass: kPerStageBindingLimits.sampledTex, perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex, },
+ readonlyStorageTex: { resource: 'readonlyStorageTex', perStageLimitClass: kPerStageBindingLimits.readonlyStorageTex, perPipelineLimitClass: kPerPipelineBindingLimits.readonlyStorageTex, },
+ writeonlyStorageTex: { resource: 'writeonlyStorageTex', perStageLimitClass: kPerStageBindingLimits.writeonlyStorageTex, perPipelineLimitClass: kPerPipelineBindingLimits.writeonlyStorageTex, },
+ readwriteStorageTex: { resource: 'readwriteStorageTex', perStageLimitClass: kPerStageBindingLimits.readwriteStorageTex, perPipelineLimitClass: kPerPipelineBindingLimits.readwriteStorageTex, },
};
// Binding type info
@@ -483,14 +495,30 @@ assertTypeTrue<TypeEqual<GPUTextureSampleType, (typeof kTextureSampleTypes)[numb
/** Binding type info (including class limits) for the specified GPUStorageTextureBindingLayout. */
export function storageTextureBindingTypeInfo(d: GPUStorageTextureBindingLayout) {
- return {
- usage: GPUConst.TextureUsage.STORAGE_BINDING,
- ...kBindingKind.storageTex,
- ...kValidStagesStorageWrite,
- };
+ switch (d.access) {
+ case undefined:
+ case 'write-only':
+ return {
+ usage: GPUConst.TextureUsage.STORAGE_BINDING,
+ ...kBindingKind.writeonlyStorageTex,
+ ...kValidStagesStorageWrite,
+ };
+ case 'read-only':
+ return {
+ usage: GPUConst.TextureUsage.STORAGE_BINDING,
+ ...kBindingKind.readonlyStorageTex,
+ ...kValidStagesAll,
+ };
+ case 'read-write':
+ return {
+ usage: GPUConst.TextureUsage.STORAGE_BINDING,
+ ...kBindingKind.readwriteStorageTex,
+ ...kValidStagesStorageWrite,
+ };
+ }
}
/** List of all GPUStorageTextureAccess values. */
-export const kStorageTextureAccessValues = ['write-only'] as const;
+export const kStorageTextureAccessValues = ['read-only', 'read-write', 'write-only'] as const;
assertTypeTrue<TypeEqual<GPUStorageTextureAccess, (typeof kStorageTextureAccessValues)[number]>>();
/** GPUBindGroupLayoutEntry, but only the "union" fields, not the common fields. */
@@ -539,8 +567,10 @@ export function samplerBindingEntries(includeUndefined: boolean): readonly BGLEn
*/
export function textureBindingEntries(includeUndefined: boolean): readonly BGLEntry[] {
return [
- ...(includeUndefined ? [{ texture: { multisampled: undefined } }] : []),
- { texture: { multisampled: false } },
+ ...(includeUndefined
+ ? [{ texture: { multisampled: undefined, sampleType: 'unfilterable-float' } } as const]
+ : []),
+ { texture: { multisampled: false, sampleType: 'unfilterable-float' } },
{ texture: { multisampled: true, sampleType: 'unfilterable-float' } },
] as const;
}
@@ -549,19 +579,17 @@ export function textureBindingEntries(includeUndefined: boolean): readonly BGLEn
*
* Note: Generates different `access` options, but not `format` or `viewDimension` options.
*/
-export function storageTextureBindingEntries(format: GPUTextureFormat): readonly BGLEntry[] {
- return [{ storageTexture: { access: 'write-only', format } }] as const;
-}
-/** Generate a list of possible texture-or-storageTexture-typed BGLEntry values. */
-export function sampledAndStorageBindingEntries(
- includeUndefined: boolean,
- storageTextureFormat: GPUTextureFormat = 'rgba8unorm'
-): readonly BGLEntry[] {
+export function storageTextureBindingEntries(): readonly BGLEntry[] {
return [
- ...textureBindingEntries(includeUndefined),
- ...storageTextureBindingEntries(storageTextureFormat),
+ { storageTexture: { access: 'write-only', format: 'r32float' } },
+ { storageTexture: { access: 'read-only', format: 'r32float' } },
+ { storageTexture: { access: 'read-write', format: 'r32float' } },
] as const;
}
+/** Generate a list of possible texture-or-storageTexture-typed BGLEntry values. */
+export function sampledAndStorageBindingEntries(includeUndefined: boolean): readonly BGLEntry[] {
+ return [...textureBindingEntries(includeUndefined), ...storageTextureBindingEntries()] as const;
+}
/**
* Generate a list of possible BGLEntry values of every type, but not variants with different:
* - buffer.hasDynamicOffset
@@ -569,14 +597,11 @@ export function sampledAndStorageBindingEntries(
* - texture.viewDimension
* - storageTexture.viewDimension
*/
-export function allBindingEntries(
- includeUndefined: boolean,
- storageTextureFormat: GPUTextureFormat = 'rgba8unorm'
-): readonly BGLEntry[] {
+export function allBindingEntries(includeUndefined: boolean): readonly BGLEntry[] {
return [
...bufferBindingEntries(includeUndefined),
...samplerBindingEntries(includeUndefined),
- ...sampledAndStorageBindingEntries(includeUndefined, storageTextureFormat),
+ ...sampledAndStorageBindingEntries(includeUndefined),
] as const;
}
@@ -689,7 +714,7 @@ const [kLimitInfoKeys, kLimitInfoDefaults, kLimitInfoData] =
'maxVertexAttributes': [ , 16, 16, ],
'maxVertexBufferArrayStride': [ , 2048, 2048, ],
'maxInterStageShaderComponents': [ , 60, 60, ],
- 'maxInterStageShaderVariables': [ , 16, 16, ],
+ 'maxInterStageShaderVariables': [ , 16, 15, ],
'maxColorAttachments': [ , 8, 4, ],
'maxColorAttachmentBytesPerSample': [ , 32, 32, ],
@@ -790,3 +815,13 @@ export const kFeatureNameInfo: {
};
/** List of all GPUFeatureName values. */
export const kFeatureNames = keysOf(kFeatureNameInfo);
+
+/** List of all known WGSL language features */
+export const kKnownWGSLLanguageFeatures = [
+ 'readonly_and_readwrite_storage_textures',
+ 'packed_4x8_integer_dot_product',
+ 'unrestricted_pointer_parameters',
+ 'pointer_composite_access',
+] as const;
+
+export type WGSLLanguageFeature = (typeof kKnownWGSLLanguageFeatures)[number];
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/createBindGroup.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/createBindGroup.spec.ts
new file mode 100644
index 0000000000..b48fa80422
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/createBindGroup.spec.ts
@@ -0,0 +1,178 @@
+export const description = `
+Tests that, in compat mode, the dimension of a view is compatible with a texture's textureBindingViewDimension.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kTextureDimensions, kTextureViewDimensions } from '../../../capability_info.js';
+import {
+ effectiveViewDimensionForTexture,
+ getTextureDimensionFromView,
+} from '../../../util/texture/base.js';
+import { CompatibilityTest } from '../../compatibility_test.js';
+
+export const g = makeTestGroup(CompatibilityTest);
+
+function isTextureBindingViewDimensionCompatibleWithDimension(
+ dimension: GPUTextureDimension = '2d',
+ textureBindingViewDimension: GPUTextureViewDimension = '2d'
+) {
+ return getTextureDimensionFromView(textureBindingViewDimension) === dimension;
+}
+
+function isValidViewDimensionForDimension(
+ dimension: GPUTextureDimension | undefined,
+ depthOrArrayLayers: number,
+ viewDimension: GPUTextureViewDimension | undefined
+) {
+ if (viewDimension === undefined) {
+ return true;
+ }
+
+ switch (dimension) {
+ case '1d':
+ return viewDimension === '1d';
+ case '2d':
+ case undefined:
+ switch (viewDimension) {
+ case undefined:
+ case '2d':
+ case '2d-array':
+ return true;
+ case 'cube':
+ return depthOrArrayLayers === 6;
+ case 'cube-array':
+ return depthOrArrayLayers % 6 === 0;
+ default:
+ return false;
+ }
+ break;
+ case '3d':
+ return viewDimension === '3d';
+ }
+}
+
+function isValidDimensionForDepthOrArrayLayers(
+ dimension: GPUTextureDimension | undefined,
+ depthOrArrayLayers: number
+) {
+ switch (dimension) {
+ case '1d':
+ return depthOrArrayLayers === 1;
+ default:
+ return true;
+ }
+}
+
+function isValidViewDimensionForDepthOrArrayLayers(
+ viewDimension: GPUTextureViewDimension | undefined,
+ depthOrArrayLayers: number
+) {
+ switch (viewDimension) {
+ case '2d':
+ return depthOrArrayLayers === 1;
+ case 'cube':
+ return depthOrArrayLayers === 6;
+ case 'cube-array':
+ return depthOrArrayLayers % 6 === 0;
+ default:
+ return true;
+ }
+ return viewDimension === 'cube';
+}
+
+function getEffectiveTextureBindingViewDimension(
+ dimension: GPUTextureDimension | undefined,
+ depthOrArrayLayers: number,
+ textureBindingViewDimension: GPUTextureViewDimension | undefined
+) {
+ if (textureBindingViewDimension) {
+ return textureBindingViewDimension;
+ }
+
+ switch (dimension) {
+ case '1d':
+ return '1d';
+ case '2d':
+ case undefined:
+ return depthOrArrayLayers > 1 ? '2d-array' : '2d';
+ break;
+ case '3d':
+ return '3d';
+ }
+}
+
+g.test('viewDimension_matches_textureBindingViewDimension')
+ .desc(
+ `
+ Tests that, in compat mode, the dimension of a view is compatible with a texture's textureBindingViewDimension
+ when used as a TEXTURE_BINDING.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('dimension', [...kTextureDimensions, undefined])
+ .combine('textureBindingViewDimension', [...kTextureViewDimensions, undefined])
+ .combine('viewDimension', [...kTextureViewDimensions, undefined])
+ .combine('depthOrArrayLayers', [1, 2, 6])
+ .filter(
+ ({ dimension, textureBindingViewDimension, depthOrArrayLayers, viewDimension }) =>
+ textureBindingViewDimension !== 'cube-array' &&
+ viewDimension !== 'cube-array' &&
+ isTextureBindingViewDimensionCompatibleWithDimension(
+ dimension,
+ textureBindingViewDimension
+ ) &&
+ isValidViewDimensionForDimension(dimension, depthOrArrayLayers, viewDimension) &&
+ isValidViewDimensionForDepthOrArrayLayers(
+ textureBindingViewDimension,
+ depthOrArrayLayers
+ ) &&
+ isValidDimensionForDepthOrArrayLayers(dimension, depthOrArrayLayers)
+ )
+ )
+ .fn(t => {
+ const { dimension, textureBindingViewDimension, viewDimension, depthOrArrayLayers } = t.params;
+
+ const texture = t.device.createTexture({
+ size: [1, 1, depthOrArrayLayers],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ ...(dimension && { dimension }),
+ ...(textureBindingViewDimension && { textureBindingViewDimension }),
+ } as GPUTextureDescriptor); // MAINTENANCE_TODO: remove cast once textureBindingViewDimension is added to IDL
+ t.trackForCleanup(texture);
+
+ const effectiveTextureBindingViewDimension = getEffectiveTextureBindingViewDimension(
+ dimension,
+ texture.depthOrArrayLayers,
+ textureBindingViewDimension
+ );
+
+ const effectiveViewDimension = getEffectiveTextureBindingViewDimension(
+ dimension,
+ texture.depthOrArrayLayers,
+ viewDimension
+ );
+
+ const layout = t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ texture: {
+ viewDimension: effectiveViewDimensionForTexture(texture, viewDimension),
+ },
+ },
+ ],
+ });
+
+ const resource = texture.createView({ dimension: viewDimension });
+ const shouldError = effectiveTextureBindingViewDimension !== effectiveViewDimension;
+
+ t.expectValidationError(() => {
+ t.device.createBindGroup({
+ layout,
+ entries: [{ binding: 0, resource }],
+ });
+ }, shouldError);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/createBindGroupLayout.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/createBindGroupLayout.spec.ts
new file mode 100644
index 0000000000..f05af2860e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/createBindGroupLayout.spec.ts
@@ -0,0 +1,34 @@
+export const description = `
+Tests that, in compat mode, you can not create a bind group layout with unsupported storage texture formats.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { kCompatModeUnsupportedStorageTextureFormats } from '../../../format_info.js';
+import { CompatibilityTest } from '../../compatibility_test.js';
+
+export const g = makeTestGroup(CompatibilityTest);
+
+g.test('unsupportedStorageTextureFormats')
+ .desc(
+ `
+ Tests that, in compat mode, you can not create a bind group layout with unsupported storage texture formats.
+ `
+ )
+ .params(u => u.combine('format', kCompatModeUnsupportedStorageTextureFormats))
+ .fn(t => {
+ const { format } = t.params;
+
+ t.expectValidationError(() => {
+ t.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ storageTexture: {
+ format,
+ },
+ },
+ ],
+ });
+ }, true);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/encoding/cmds/copyTextureToBuffer.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/encoding/cmds/copyTextureToBuffer.spec.ts
index a9af7795b3..e7d5164c45 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/encoding/cmds/copyTextureToBuffer.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/encoding/cmds/copyTextureToBuffer.spec.ts
@@ -19,16 +19,17 @@ g.test('compressed')
.fn(t => {
const { format } = t.params;
- const { blockWidth, blockHeight, bytesPerBlock } = kTextureFormatInfo[format];
+ const info = kTextureFormatInfo[format];
+ const textureSize = [info.blockWidth, info.blockHeight, 1];
const texture = t.device.createTexture({
- size: [blockWidth, blockHeight, 1],
+ size: textureSize,
format,
usage: GPUTextureUsage.COPY_SRC,
});
t.trackForCleanup(texture);
- const bytesPerRow = align(bytesPerBlock, 256);
+ const bytesPerRow = align(info.color.bytes, 256);
const buffer = t.device.createBuffer({
size: bytesPerRow,
@@ -37,7 +38,7 @@ g.test('compressed')
t.trackForCleanup(buffer);
const encoder = t.device.createCommandEncoder();
- encoder.copyTextureToBuffer({ texture }, { buffer, bytesPerRow }, [blockWidth, blockHeight, 1]);
+ encoder.copyTextureToBuffer({ texture }, { buffer, bytesPerRow }, textureSize);
t.expectGPUError('validation', () => {
encoder.finish();
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/encoding/cmds/copyTextureToTexture.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/encoding/cmds/copyTextureToTexture.spec.ts
new file mode 100644
index 0000000000..cd551f9b63
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/encoding/cmds/copyTextureToTexture.spec.ts
@@ -0,0 +1,94 @@
+export const description = `
+Tests limitations of copyTextureToTextures in compat mode.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import {
+ kAllTextureFormats,
+ kCompressedTextureFormats,
+ kTextureFormatInfo,
+} from '../../../../../format_info.js';
+import { CompatibilityTest } from '../../../../compatibility_test.js';
+
+export const g = makeTestGroup(CompatibilityTest);
+
+g.test('compressed')
+ .desc(`Tests that you can not call copyTextureToTexture with compressed textures in compat mode.`)
+ .params(u => u.combine('format', kCompressedTextureFormats))
+ .beforeAllSubcases(t => {
+ const { format } = t.params;
+ t.selectDeviceOrSkipTestCase([kTextureFormatInfo[format].feature]);
+ })
+ .fn(t => {
+ const { format } = t.params;
+
+ const { blockWidth, blockHeight } = kTextureFormatInfo[format];
+
+ const srcTexture = t.device.createTexture({
+ size: [blockWidth, blockHeight, 1],
+ format,
+ usage: GPUTextureUsage.COPY_SRC,
+ });
+ t.trackForCleanup(srcTexture);
+
+ const dstTexture = t.device.createTexture({
+ size: [blockWidth, blockHeight, 1],
+ format,
+ usage: GPUTextureUsage.COPY_DST,
+ });
+ t.trackForCleanup(dstTexture);
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyTextureToTexture({ texture: srcTexture }, { texture: dstTexture }, [
+ blockWidth,
+ blockHeight,
+ 1,
+ ]);
+ t.expectGPUError('validation', () => {
+ encoder.finish();
+ });
+ });
+
+g.test('multisample')
+ .desc(`Test that you can not call copyTextureToTexture with multisample textures in compat mode.`)
+ .params(u =>
+ u
+ .beginSubcases()
+ .combine('format', kAllTextureFormats)
+ .filter(({ format }) => {
+ const info = kTextureFormatInfo[format];
+ return info.multisample && !info.feature;
+ })
+ )
+ .fn(t => {
+ const { format } = t.params;
+ const { blockWidth, blockHeight } = kTextureFormatInfo[format];
+
+ t.skipIfTextureFormatNotSupported(format as GPUTextureFormat);
+
+ const srcTexture = t.device.createTexture({
+ size: [blockWidth, blockHeight, 1],
+ format,
+ sampleCount: 4,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+ t.trackForCleanup(srcTexture);
+
+ const dstTexture = t.device.createTexture({
+ size: [blockWidth, blockHeight, 1],
+ format,
+ sampleCount: 4,
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+ t.trackForCleanup(dstTexture);
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyTextureToTexture({ texture: srcTexture }, { texture: dstTexture }, [
+ blockWidth,
+ blockHeight,
+ 1,
+ ]);
+ t.expectGPUError('validation', () => {
+ encoder.finish();
+ });
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/render_pipeline/depth_stencil_state.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/render_pipeline/depth_stencil_state.spec.ts
new file mode 100644
index 0000000000..66045c2b8a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/render_pipeline/depth_stencil_state.spec.ts
@@ -0,0 +1,53 @@
+export const description = `
+Tests that depthBiasClamp must be zero in compat mode.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { CompatibilityTest } from '../../../compatibility_test.js';
+
+export const g = makeTestGroup(CompatibilityTest);
+
+g.test('depthBiasClamp')
+ .desc('Tests that depthBiasClamp must be zero in compat mode.')
+ .params(u =>
+ u //
+ .combine('depthBiasClamp', [undefined, 0, 0.1, 1])
+ .combine('async', [false, true] as const)
+ )
+ .fn(t => {
+ const { depthBiasClamp, async } = t.params;
+
+ const module = t.device.createShaderModule({
+ code: `
+ @vertex fn vs() -> @builtin(position) vec4f {
+ return vec4f(0);
+ }
+
+ @fragment fn fs() -> @location(0) vec4f {
+ return vec4f(0);
+ }
+ `,
+ });
+
+ const pipelineDescriptor: GPURenderPipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs',
+ },
+ fragment: {
+ module,
+ entryPoint: 'fs',
+ targets: [{ format: 'rgba8unorm' }],
+ },
+ depthStencil: {
+ format: 'depth24plus',
+ depthWriteEnabled: true,
+ depthCompare: 'always',
+ ...(depthBiasClamp !== undefined && { depthBiasClamp }),
+ },
+ };
+
+ const success = !depthBiasClamp;
+ t.doCreateRenderPipelineTest(async, success, pipelineDescriptor);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/render_pipeline/shader_module.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/render_pipeline/shader_module.spec.ts
index abe2b063e7..56da7d355e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/render_pipeline/shader_module.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/render_pipeline/shader_module.spec.ts
@@ -3,6 +3,7 @@ Tests limitations of createRenderPipeline related to shader modules in compat mo
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { kCompatModeUnsupportedStorageTextureFormats } from '../../../../format_info.js';
import { CompatibilityTest } from '../../../compatibility_test.js';
export const g = makeTestGroup(CompatibilityTest);
@@ -72,3 +73,209 @@ Tests that you can not create a render pipeline with a shader module that uses s
!isValid
);
});
+
+g.test('sample_index')
+ .desc(
+ `
+Tests that you can not create a render pipeline with a shader module that uses sample_index in compat mode.
+
+- Test that a pipeline with a shader that uses sample_index fails.
+- Test that a pipeline that references a module that has a shader that uses sample_index
+ but the pipeline does not reference that shader succeeds.
+ `
+ )
+ .params(u =>
+ u.combine('entryPoint', ['fsWithoutSampleIndexUsage', 'fsWithSampleIndexUsage'] as const)
+ )
+ .fn(t => {
+ const { entryPoint } = t.params;
+
+ const module = t.device.createShaderModule({
+ code: `
+ @vertex fn vs() -> @builtin(position) vec4f {
+ return vec4f(1);
+ }
+ @fragment fn fsWithoutSampleIndexUsage() -> @location(0) vec4f {
+ return vec4f(0);
+ }
+ @fragment fn fsWithSampleIndexUsage(@builtin(sample_index) sampleIndex: u32) -> @location(0) vec4f {
+ _ = sampleIndex;
+ return vec4f(0);
+ }
+ `,
+ });
+
+ const pipelineDescriptor: GPURenderPipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs',
+ },
+ fragment: {
+ module,
+ entryPoint,
+ targets: [
+ {
+ format: 'rgba8unorm',
+ },
+ ],
+ },
+ multisample: {
+ count: 4,
+ },
+ };
+
+ const isValid = entryPoint === 'fsWithoutSampleIndexUsage';
+ t.expectGPUError(
+ 'validation',
+ () => t.device.createRenderPipeline(pipelineDescriptor),
+ !isValid
+ );
+ });
+
+g.test('interpolate')
+ .desc(
+ `
+Tests that you can not create a render pipeline with a shader module that uses interpolate(linear) nor interpolate(...,sample) in compat mode.
+
+- Test that a pipeline with a shader that uses interpolate(linear) or interpolate(sample) fails.
+- Test that a pipeline that references a module that has a shader that uses interpolate(linear/sample)
+ but the pipeline does not reference that shader succeeds.
+ `
+ )
+ .params(u =>
+ u
+ .combine('interpolate', [
+ '',
+ '@interpolate(linear)',
+ '@interpolate(linear, sample)',
+ '@interpolate(perspective, sample)',
+ ] as const)
+ .combine('entryPoint', [
+ 'fsWithoutInterpolationUsage',
+ 'fsWithInterpolationUsage1',
+ 'fsWithInterpolationUsage2',
+ 'fsWithInterpolationUsage3',
+ ] as const)
+ )
+ .fn(t => {
+ const { entryPoint, interpolate } = t.params;
+
+ const module = t.device.createShaderModule({
+ code: `
+ struct Vertex {
+ @builtin(position) pos: vec4f,
+ @location(0) ${interpolate} color : vec4f,
+ };
+ @vertex fn vs() -> Vertex {
+ var v: Vertex;
+ v.pos = vec4f(1);
+ v.color = vec4f(1);
+ return v;
+ }
+ @fragment fn fsWithoutInterpolationUsage() -> @location(0) vec4f {
+ return vec4f(1);
+ }
+ @fragment fn fsWithInterpolationUsage1(v: Vertex) -> @location(0) vec4f {
+ return vec4f(1);
+ }
+ @fragment fn fsWithInterpolationUsage2(v: Vertex) -> @location(0) vec4f {
+ return v.pos;
+ }
+ @fragment fn fsWithInterpolationUsage3(v: Vertex) -> @location(0) vec4f {
+ return v.color;
+ }
+ `,
+ });
+
+ const pipelineDescriptor: GPURenderPipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs',
+ },
+ fragment: {
+ module,
+ entryPoint,
+ targets: [
+ {
+ format: 'rgba8unorm',
+ },
+ ],
+ },
+ };
+
+ const isValid = entryPoint === 'fsWithoutInterpolationUsage' || interpolate === '';
+ t.expectGPUError(
+ 'validation',
+ () => t.device.createRenderPipeline(pipelineDescriptor),
+ !isValid
+ );
+ });
+
+g.test('unsupportedStorageTextureFormats,computePipeline')
+ .desc(
+ `
+Tests that you can not create a compute pipeline with unsupported storage texture formats in compat mode.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('format', kCompatModeUnsupportedStorageTextureFormats)
+ .combine('async', [false, true] as const)
+ )
+ .fn(t => {
+ const { format, async } = t.params;
+
+ const module = t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var s: texture_storage_2d<${format}, read>;
+ @compute @workgroup_size(1) fn cs() {
+ _ = textureLoad(s, vec2u(0));
+ }
+ `,
+ });
+
+ const pipelineDescriptor: GPUComputePipelineDescriptor = {
+ layout: 'auto',
+ compute: {
+ module,
+ entryPoint: 'cs',
+ },
+ };
+ t.doCreateComputePipelineTest(async, false, pipelineDescriptor);
+ });
+
+g.test('unsupportedStorageTextureFormats,renderPipeline')
+ .desc(
+ `
+Tests that you can not create a render pipeline with unsupported storage texture formats in compat mode.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('format', kCompatModeUnsupportedStorageTextureFormats)
+ .combine('async', [false, true] as const)
+ )
+ .fn(t => {
+ const { format, async } = t.params;
+
+ const module = t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var s: texture_storage_2d<${format}, read>;
+ @vertex fn vs() -> @builtin(position) vec4f {
+ _ = textureLoad(s, vec2u(0));
+ return vec4f(0);
+ }
+ `,
+ });
+
+ const pipelineDescriptor: GPURenderPipelineDescriptor = {
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs',
+ },
+ };
+ t.doCreateRenderPipelineTest(async, false, pipelineDescriptor);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/texture/createTexture.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/texture/createTexture.spec.ts
index 9f0d353268..58dcd41ec7 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/texture/createTexture.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/compat/api/validation/texture/createTexture.spec.ts
@@ -1,8 +1,13 @@
+/* eslint-disable prettier/prettier */
export const description = `
Tests that you can not use bgra8unorm-srgb in compat mode.
+Tests that textureBindingViewDimension must compatible with texture dimension
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { kTextureDimensions, kTextureViewDimensions } from '../../../../capability_info.js';
+import { kColorTextureFormats, kCompatModeUnsupportedStorageTextureFormats, kTextureFormatInfo } from '../../../../format_info.js';
+import { getTextureDimensionFromView } from '../../../../util/texture/base.js';
import { CompatibilityTest } from '../../../compatibility_test.js';
export const g = makeTestGroup(CompatibilityTest);
@@ -39,3 +44,129 @@ g.test('unsupportedTextureViewFormats')
true
);
});
+
+g.test('invalidTextureBindingViewDimension')
+ .desc(
+ `Tests that you can not specify a textureBindingViewDimension that is incompatible with the texture's dimension.`
+ )
+ .params(u =>
+ u //
+ .combine('dimension', kTextureDimensions)
+ .combine('textureBindingViewDimension', kTextureViewDimensions)
+ )
+ .fn(t => {
+ const { dimension, textureBindingViewDimension } = t.params;
+ const depthOrArrayLayers = textureBindingViewDimension === '1d' || textureBindingViewDimension === '2d' ? 1 : 6;
+ const shouldError = getTextureDimensionFromView(textureBindingViewDimension) !== dimension;
+ t.expectGPUError(
+ 'validation',
+ () => {
+ const texture = t.device.createTexture({
+ size: [1, 1, depthOrArrayLayers],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ dimension,
+ textureBindingViewDimension,
+ } as GPUTextureDescriptor); // MAINTENANCE_TODO: remove cast once textureBindingViewDimension is added to IDL
+ t.trackForCleanup(texture);
+ },
+ shouldError
+ );
+ });
+
+g.test('depthOrArrayLayers_incompatible_with_textureBindingViewDimension')
+ .desc(
+ `Tests
+ * if textureBindingViewDimension is '2d' then depthOrArrayLayers must be 1
+ * if textureBindingViewDimension is 'cube' then depthOrArrayLayers must be 6
+ `
+ )
+ .params(u =>
+ u //
+ .combine('textureBindingViewDimension', ['2d', 'cube'])
+ .combine('depthOrArrayLayers', [1, 3, 6, 12])
+ )
+ .fn(t => {
+ const { textureBindingViewDimension, depthOrArrayLayers } = t.params;
+ const shouldError =
+ (textureBindingViewDimension === '2d' && depthOrArrayLayers !== 1) ||
+ (textureBindingViewDimension === 'cube' && depthOrArrayLayers !== 6);
+ t.expectGPUError(
+ 'validation',
+ () => {
+ const texture = t.device.createTexture({
+ size: [1, 1, depthOrArrayLayers],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ textureBindingViewDimension,
+ } as GPUTextureDescriptor); // MAINTENANCE_TODO: remove cast once textureBindingViewDimension is added to IDL
+ t.trackForCleanup(texture);
+ },
+ shouldError
+ );
+ });
+
+g.test('format_reinterpretation')
+ .desc(
+ `
+ Tests that you can not request different view formats when creating a texture.
+ For example, rgba8unorm can not be viewed as rgba8unorm-srgb
+ `
+ )
+ .params(u =>
+ u //
+ .combine('format', kColorTextureFormats as GPUTextureFormat[])
+ .filter(
+ ({ format }) =>
+ !!kTextureFormatInfo[format].baseFormat &&
+ kTextureFormatInfo[format].baseFormat !== format
+ )
+ )
+ .beforeAllSubcases(t => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+ })
+ .fn(t => {
+ const { format } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const formatPairs = [
+ { format, viewFormats: [info.baseFormat!] },
+ { format: info.baseFormat!, viewFormats: [format] },
+ { format, viewFormats: [format, info.baseFormat!] },
+ { format: info.baseFormat!, viewFormats: [format, info.baseFormat!] },
+ ];
+ for (const { format, viewFormats } of formatPairs) {
+ t.expectGPUError(
+ 'validation',
+ () => {
+ const texture = t.device.createTexture({
+ size: [info.blockWidth, info.blockHeight],
+ format,
+ viewFormats,
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ });
+ t.trackForCleanup(texture);
+ },
+ true
+ );
+ }
+ });
+
+g.test('unsupportedStorageTextureFormats')
+ .desc(`Tests that you can not create unsupported storage texture formats in compat mode.`)
+ .params(u => u.combine('format', kCompatModeUnsupportedStorageTextureFormats))
+ .fn(t => {
+ const { format } = t.params;
+ t.expectGPUError(
+ 'validation',
+ () =>
+ t.device.createTexture({
+ size: [1, 1, 1],
+ format,
+ usage: GPUTextureUsage.STORAGE_BINDING,
+ }),
+ true
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/constants.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/constants.ts
index c7a28cb837..60cdd63674 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/constants.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/constants.ts
@@ -60,3 +60,8 @@ export const GPUConst = {
export const kMaxUnsignedLongValue = 4294967295;
export const kMaxUnsignedLongLongValue = Number.MAX_SAFE_INTEGER;
+
+export const kInterpolationSampling = ['center', 'centroid', 'sample'] as const;
+export const kInterpolationType = ['perspective', 'linear', 'flat'] as const;
+export type InterpolationType = (typeof kInterpolationType)[number];
+export type InterpolationSampling = (typeof kInterpolationSampling)[number];
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/format_info.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/format_info.ts
index 566027714f..be1320d3cd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/format_info.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/format_info.ts
@@ -1,5 +1,5 @@
import { keysOf } from '../common/util/data_tables.js';
-import { assert } from '../common/util/util.js';
+import { assert, unreachable } from '../common/util/util.js';
import { align } from './util/math.js';
import { ImageCopyType } from './util/texture/layout.js';
@@ -13,25 +13,26 @@ import { ImageCopyType } from './util/texture/layout.js';
* `formatTableWithDefaults`. This ensures keys are never missing, always explicitly `undefined`.
*
* All top-level keys must be defined here, or they won't be exposed at all.
+ * Documentation is also written here; this makes it propagate through to the end types.
*/
const kFormatUniversalDefaults = {
+ /** Texel block width. */
blockWidth: undefined,
+ /** Texel block height. */
blockHeight: undefined,
color: undefined,
depth: undefined,
stencil: undefined,
colorRender: undefined,
+ /** Whether the format can be used in a multisample texture. */
multisample: undefined,
+ /** Optional feature required to use this format, or `undefined` if none. */
feature: undefined,
+ /** The base format for srgb formats. Specified on both srgb and equivalent non-srgb formats. */
baseFormat: undefined,
- sampleType: undefined,
- copySrc: undefined,
- copyDst: undefined,
+ /** @deprecated Use `.color.bytes`, `.depth.bytes`, or `.stencil.bytes`. */
bytesPerBlock: undefined,
- renderable: false,
- renderTargetPixelByteCost: undefined,
- renderTargetComponentAlignment: undefined,
// IMPORTANT:
// Add new top-level keys both here and in TextureFormatInfo_TypeCheck.
@@ -67,382 +68,506 @@ function formatTableWithDefaults<Defaults extends {}, Table extends { readonly [
/** "plain color formats", plus rgb9e5ufloat. */
const kRegularTextureFormatInfo = formatTableWithDefaults({
- defaults: { blockWidth: 1, blockHeight: 1, copySrc: true, copyDst: true },
+ defaults: { blockWidth: 1, blockHeight: 1 },
table: {
// plain, 8 bits per component
r8unorm: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 1 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 1,
+ },
colorRender: { blend: true, resolve: true, byteCost: 1, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
r8snorm: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 1 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 1,
+ },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
r8uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 1 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 1,
+ },
colorRender: { blend: false, resolve: false, byteCost: 1, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
r8sint: {
- color: { type: 'sint', copySrc: true, copyDst: true, storage: false, bytes: 1 },
+ color: {
+ type: 'sint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 1,
+ },
colorRender: { blend: false, resolve: false, byteCost: 1, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg8unorm: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 2,
+ },
colorRender: { blend: true, resolve: true, byteCost: 2, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg8snorm: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 2,
+ },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg8uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 2,
+ },
colorRender: { blend: false, resolve: false, byteCost: 2, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg8sint: {
- color: { type: 'sint', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ color: {
+ type: 'sint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 2,
+ },
colorRender: { blend: false, resolve: false, byteCost: 2, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba8unorm: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: true, resolve: true, byteCost: 8, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
baseFormat: 'rgba8unorm',
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'rgba8unorm-srgb': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: true, resolve: true, byteCost: 8, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
baseFormat: 'rgba8unorm',
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba8snorm: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 4,
+ },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba8uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba8sint: {
- color: { type: 'sint', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ color: {
+ type: 'sint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
bgra8unorm: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: true, resolve: true, byteCost: 8, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
baseFormat: 'bgra8unorm',
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bgra8unorm-srgb': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: true, resolve: true, byteCost: 8, alignment: 1 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
baseFormat: 'bgra8unorm',
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
// plain, 16 bits per component
r16uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 2,
+ },
colorRender: { blend: false, resolve: false, byteCost: 2, alignment: 2 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
r16sint: {
- color: { type: 'sint', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ color: {
+ type: 'sint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 2,
+ },
colorRender: { blend: false, resolve: false, byteCost: 2, alignment: 2 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
r16float: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 2 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 2,
+ },
colorRender: { blend: true, resolve: true, byteCost: 2, alignment: 2 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg16uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 2 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg16sint: {
- color: { type: 'sint', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'sint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 2 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg16float: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: true, resolve: true, byteCost: 4, alignment: 2 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba16uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: true, bytes: 8 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 8,
+ },
colorRender: { blend: false, resolve: false, byteCost: 8, alignment: 2 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba16sint: {
- color: { type: 'sint', copySrc: true, copyDst: true, storage: true, bytes: 8 },
+ color: {
+ type: 'sint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 8,
+ },
colorRender: { blend: false, resolve: false, byteCost: 8, alignment: 2 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba16float: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: true, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 8,
+ },
colorRender: { blend: true, resolve: true, byteCost: 8, alignment: 2 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
// plain, 32 bits per component
r32uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: true,
+ bytes: 4,
+ },
colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
r32sint: {
- color: { type: 'sint', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ color: {
+ type: 'sint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: true,
+ bytes: 4,
+ },
colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
r32float: {
- color: { type: 'unfilterable-float', copySrc: true, copyDst: true, storage: true, bytes: 4 },
+ color: {
+ type: 'unfilterable-float',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: true,
+ bytes: 4,
+ },
colorRender: { blend: false, resolve: false, byteCost: 4, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg32uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: true, bytes: 8 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 8,
+ },
colorRender: { blend: false, resolve: false, byteCost: 8, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg32sint: {
- color: { type: 'sint', copySrc: true, copyDst: true, storage: true, bytes: 8 },
+ color: {
+ type: 'sint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 8,
+ },
colorRender: { blend: false, resolve: false, byteCost: 8, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg32float: {
- color: { type: 'unfilterable-float', copySrc: true, copyDst: true, storage: true, bytes: 8 },
+ color: {
+ type: 'unfilterable-float',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 8,
+ },
colorRender: { blend: false, resolve: false, byteCost: 8, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba32uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: true, bytes: 16 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 16,
+ },
colorRender: { blend: false, resolve: false, byteCost: 16, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba32sint: {
- color: { type: 'sint', copySrc: true, copyDst: true, storage: true, bytes: 16 },
+ color: {
+ type: 'sint',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 16,
+ },
colorRender: { blend: false, resolve: false, byteCost: 16, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgba32float: {
- color: { type: 'unfilterable-float', copySrc: true, copyDst: true, storage: true, bytes: 16 },
+ color: {
+ type: 'unfilterable-float',
+ copySrc: true,
+ copyDst: true,
+ storage: true,
+ readWriteStorage: false,
+ bytes: 16,
+ },
colorRender: { blend: false, resolve: false, byteCost: 16, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
// plain, mixed component width, 32 bits per texel
rgb10a2uint: {
- color: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: false, resolve: false, byteCost: 8, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rgb10a2unorm: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
colorRender: { blend: true, resolve: true, byteCost: 8, alignment: 4 },
- renderable: true,
- /*prettier-ignore*/ get renderTargetComponentAlignment() { return this.colorRender.alignment; },
- /*prettier-ignore*/ get renderTargetPixelByteCost() { return this.colorRender.byteCost; },
multisample: true,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
rg11b10ufloat: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
- renderTargetPixelByteCost: 8,
- renderTargetComponentAlignment: 4,
},
// packed
rgb9e5ufloat: {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 4 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
multisample: false,
- /*prettier-ignore*/ get sampleType() { return this.color.type; },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
},
@@ -452,24 +577,39 @@ const kRegularTextureFormatInfo = formatTableWithDefaults({
// because one aspect can be sized and one can be unsized. This should be cleaned up, but is kept
// this way during a migration phase.
const kSizedDepthStencilFormatInfo = formatTableWithDefaults({
- defaults: { blockWidth: 1, blockHeight: 1, multisample: true, copySrc: true, renderable: true },
+ defaults: { blockWidth: 1, blockHeight: 1, multisample: true },
table: {
stencil8: {
- stencil: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 1 },
- sampleType: 'uint',
- copyDst: true,
+ stencil: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 1,
+ },
bytesPerBlock: 1,
},
depth16unorm: {
- depth: { type: 'depth', copySrc: true, copyDst: true, storage: false, bytes: 2 },
- sampleType: 'depth',
- copyDst: true,
+ depth: {
+ type: 'depth',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 2,
+ },
bytesPerBlock: 2,
},
depth32float: {
- depth: { type: 'depth', copySrc: true, copyDst: false, storage: false, bytes: 4 },
- sampleType: 'depth',
- copyDst: false,
+ depth: {
+ type: 'depth',
+ copySrc: true,
+ copyDst: false,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
bytesPerBlock: 4,
},
},
@@ -478,28 +618,51 @@ const kUnsizedDepthStencilFormatInfo = formatTableWithDefaults({
defaults: { blockWidth: 1, blockHeight: 1, multisample: true },
table: {
depth24plus: {
- depth: { type: 'depth', copySrc: false, copyDst: false, storage: false, bytes: undefined },
- copySrc: false,
- copyDst: false,
- sampleType: 'depth',
- renderable: true,
+ depth: {
+ type: 'depth',
+ copySrc: false,
+ copyDst: false,
+ storage: false,
+ readWriteStorage: false,
+ bytes: undefined,
+ },
},
'depth24plus-stencil8': {
- depth: { type: 'depth', copySrc: false, copyDst: false, storage: false, bytes: undefined },
- stencil: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 1 },
- copySrc: false,
- copyDst: false,
- sampleType: 'depth',
- renderable: true,
+ depth: {
+ type: 'depth',
+ copySrc: false,
+ copyDst: false,
+ storage: false,
+ readWriteStorage: false,
+ bytes: undefined,
+ },
+ stencil: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 1,
+ },
},
'depth32float-stencil8': {
- depth: { type: 'depth', copySrc: true, copyDst: false, storage: false, bytes: 4 },
- stencil: { type: 'uint', copySrc: true, copyDst: true, storage: false, bytes: 1 },
+ depth: {
+ type: 'depth',
+ copySrc: true,
+ copyDst: false,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 4,
+ },
+ stencil: {
+ type: 'uint',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 1,
+ },
feature: 'depth32float-stencil8',
- copySrc: false,
- copyDst: false,
- sampleType: 'depth',
- renderable: true,
},
},
} as const);
@@ -510,78 +673,173 @@ const kBCTextureFormatInfo = formatTableWithDefaults({
blockHeight: 4,
multisample: false,
feature: 'texture-compression-bc',
- sampleType: 'float',
- copySrc: true,
- copyDst: true,
},
table: {
'bc1-rgba-unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
baseFormat: 'bc1-rgba-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc1-rgba-unorm-srgb': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
baseFormat: 'bc1-rgba-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc2-rgba-unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'bc2-rgba-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc2-rgba-unorm-srgb': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'bc2-rgba-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc3-rgba-unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'bc3-rgba-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc3-rgba-unorm-srgb': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'bc3-rgba-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc4-r-unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc4-r-snorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc5-rg-unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc5-rg-snorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc6h-rgb-ufloat': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc6h-rgb-float': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc7-rgba-unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'bc7-rgba-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'bc7-rgba-unorm-srgb': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'bc7-rgba-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -594,59 +852,126 @@ const kETC2TextureFormatInfo = formatTableWithDefaults({
blockHeight: 4,
multisample: false,
feature: 'texture-compression-etc2',
- sampleType: 'float',
- copySrc: true,
- copyDst: true,
},
table: {
'etc2-rgb8unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
baseFormat: 'etc2-rgb8unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'etc2-rgb8unorm-srgb': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
baseFormat: 'etc2-rgb8unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'etc2-rgb8a1unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
baseFormat: 'etc2-rgb8a1unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'etc2-rgb8a1unorm-srgb': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
baseFormat: 'etc2-rgb8a1unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'etc2-rgba8unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'etc2-rgba8unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'etc2-rgba8unorm-srgb': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'etc2-rgba8unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'eac-r11unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'eac-r11snorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 8 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 8,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'eac-rg11unorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'eac-rg11snorm': {
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
},
@@ -656,22 +981,33 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
defaults: {
multisample: false,
feature: 'texture-compression-astc',
- sampleType: 'float',
- copySrc: true,
- copyDst: true,
},
table: {
'astc-4x4-unorm': {
blockWidth: 4,
blockHeight: 4,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-4x4-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-4x4-unorm-srgb': {
blockWidth: 4,
blockHeight: 4,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-4x4-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -679,14 +1015,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-5x4-unorm': {
blockWidth: 5,
blockHeight: 4,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-5x4-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-5x4-unorm-srgb': {
blockWidth: 5,
blockHeight: 4,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-5x4-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -694,14 +1044,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-5x5-unorm': {
blockWidth: 5,
blockHeight: 5,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-5x5-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-5x5-unorm-srgb': {
blockWidth: 5,
blockHeight: 5,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-5x5-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -709,14 +1073,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-6x5-unorm': {
blockWidth: 6,
blockHeight: 5,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-6x5-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-6x5-unorm-srgb': {
blockWidth: 6,
blockHeight: 5,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-6x5-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -724,14 +1102,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-6x6-unorm': {
blockWidth: 6,
blockHeight: 6,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-6x6-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-6x6-unorm-srgb': {
blockWidth: 6,
blockHeight: 6,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-6x6-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -739,14 +1131,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-8x5-unorm': {
blockWidth: 8,
blockHeight: 5,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-8x5-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-8x5-unorm-srgb': {
blockWidth: 8,
blockHeight: 5,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-8x5-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -754,14 +1160,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-8x6-unorm': {
blockWidth: 8,
blockHeight: 6,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-8x6-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-8x6-unorm-srgb': {
blockWidth: 8,
blockHeight: 6,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-8x6-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -769,14 +1189,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-8x8-unorm': {
blockWidth: 8,
blockHeight: 8,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-8x8-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-8x8-unorm-srgb': {
blockWidth: 8,
blockHeight: 8,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-8x8-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -784,14 +1218,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-10x5-unorm': {
blockWidth: 10,
blockHeight: 5,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-10x5-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-10x5-unorm-srgb': {
blockWidth: 10,
blockHeight: 5,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-10x5-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -799,14 +1247,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-10x6-unorm': {
blockWidth: 10,
blockHeight: 6,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-10x6-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-10x6-unorm-srgb': {
blockWidth: 10,
blockHeight: 6,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-10x6-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -814,14 +1276,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-10x8-unorm': {
blockWidth: 10,
blockHeight: 8,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-10x8-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-10x8-unorm-srgb': {
blockWidth: 10,
blockHeight: 8,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-10x8-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -829,14 +1305,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-10x10-unorm': {
blockWidth: 10,
blockHeight: 10,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-10x10-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-10x10-unorm-srgb': {
blockWidth: 10,
blockHeight: 10,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-10x10-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -844,14 +1334,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-12x10-unorm': {
blockWidth: 12,
blockHeight: 10,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-12x10-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-12x10-unorm-srgb': {
blockWidth: 12,
blockHeight: 10,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-12x10-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -859,14 +1363,28 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
'astc-12x12-unorm': {
blockWidth: 12,
blockHeight: 12,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-12x12-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
'astc-12x12-unorm-srgb': {
blockWidth: 12,
blockHeight: 12,
- color: { type: 'float', copySrc: true, copyDst: true, storage: false, bytes: 16 },
+ color: {
+ type: 'float',
+ copySrc: true,
+ copyDst: true,
+ storage: false,
+ readWriteStorage: false,
+ bytes: 16,
+ },
baseFormat: 'astc-12x12-unorm',
/*prettier-ignore*/ get bytesPerBlock() { return this.color.bytes; },
},
@@ -921,13 +1439,6 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({
export const kRenderableColorTextureFormats = kRegularTextureFormats.filter(
v => kColorTextureFormatInfo[v].colorRender
);
-assert(
- kRenderableColorTextureFormats.every(
- f =>
- kAllTextureFormatInfo[f].renderTargetComponentAlignment !== undefined &&
- kAllTextureFormatInfo[f].renderTargetPixelByteCost !== undefined
- )
-);
/** Per-GPUTextureFormat-per-aspect info. */
interface TextureFormatAspectInfo {
@@ -937,6 +1448,8 @@ interface TextureFormatAspectInfo {
copyDst: boolean;
/** Whether the aspect can be used as `STORAGE`. */
storage: boolean;
+ /** Whether the aspect can be used as `STORAGE` with `read-write` storage texture access. */
+ readWriteStorage: boolean;
/** The "texel block copy footprint" of one texel block; `undefined` if the aspect is unsized. */
bytes: number | undefined;
}
@@ -962,34 +1475,15 @@ interface TextureFormatStencilAspectInfo extends TextureFormatAspectInfo {
* This is not actually the type of values in kTextureFormatInfo; that type is fully const
* so that it can be narrowed very precisely at usage sites by the compiler.
* This type exists only as a type check on the inferred type of kTextureFormatInfo.
- * Documentation is also written here, but not actually visible to the IDE.
*/
type TextureFormatInfo_TypeCheck = {
- /** Texel block width. */
blockWidth: number;
- /** Texel block height. */
blockHeight: number;
- /** Whether the format can be used in a multisample texture. */
multisample: boolean;
- /** The base format for srgb formats. Specified on both srgb and equivalent non-srgb formats. */
baseFormat: GPUTextureFormat | undefined;
- /** Optional feature required to use this format, or `undefined` if none. */
feature: GPUFeatureName | undefined;
- /** @deprecated */
- sampleType: GPUTextureSampleType;
- /** @deprecated */
- copySrc: boolean;
- /** @deprecated */
- copyDst: boolean;
- /** @deprecated */
bytesPerBlock: number | undefined;
- /** @deprecated */
- renderable: boolean;
- /** @deprecated */
- renderTargetPixelByteCost: number | undefined;
- /** @deprecated */
- renderTargetComponentAlignment: number | undefined;
// IMPORTANT:
// Add new top-level keys both here and in kUniversalDefaults.
@@ -1043,10 +1537,6 @@ const kTextureFormatInfo_TypeCheck: {
readonly [F in GPUTextureFormat]: TextureFormatInfo_TypeCheck;
} = kTextureFormatInfo;
-/** List of all GPUTextureFormat values. */
-// MAINTENANCE_TODO: dedup with kAllTextureFormats
-export const kTextureFormats: readonly GPUTextureFormat[] = keysOf(kAllTextureFormatInfo);
-
/** Valid GPUTextureFormats for `copyExternalImageToTexture`, by spec. */
export const kValidTextureFormatsForCopyE2T = [
'r8unorm',
@@ -1170,6 +1660,35 @@ export function resolvePerAspectFormat(
}
/**
+ * @returns the sample type of the specified aspect of the specified format.
+ */
+export function sampleTypeForFormatAndAspect(
+ format: GPUTextureFormat,
+ aspect: GPUTextureAspect
+): 'uint' | 'depth' | 'float' | 'sint' | 'unfilterable-float' {
+ const info = kTextureFormatInfo[format];
+ if (info.color) {
+ assert(aspect === 'all', `color format ${format} used with aspect ${aspect}`);
+ return info.color.type;
+ } else if (info.depth && info.stencil) {
+ if (aspect === 'depth-only') {
+ return info.depth.type;
+ } else if (aspect === 'stencil-only') {
+ return info.stencil.type;
+ } else {
+ unreachable(`depth-stencil format ${format} used with aspect ${aspect}`);
+ }
+ } else if (info.depth) {
+ assert(aspect !== 'stencil-only', `depth-only format ${format} used with aspect ${aspect}`);
+ return info.depth.type;
+ } else if (info.stencil) {
+ assert(aspect !== 'depth-only', `stencil-only format ${format} used with aspect ${aspect}`);
+ return info.stencil.type;
+ }
+ unreachable();
+}
+
+/**
* Gets all copyable aspects for copies between texture and buffer for specified depth/stencil format and copy type, by spec.
*/
export function depthStencilFormatCopyableAspects(
@@ -1229,8 +1748,12 @@ export function textureDimensionAndFormatCompatible(
*
* This function may need to be generalized to use `baseFormat` from `kTextureFormatInfo`.
*/
-export function viewCompatible(a: GPUTextureFormat, b: GPUTextureFormat): boolean {
- return a === b || a + '-srgb' === b || b + '-srgb' === a;
+export function viewCompatible(
+ compatibilityMode: boolean,
+ a: GPUTextureFormat,
+ b: GPUTextureFormat
+): boolean {
+ return compatibilityMode ? a === b : a === b || a + '-srgb' === b || b + '-srgb' === a;
}
export function getFeaturesForFormats<T>(
@@ -1250,7 +1773,29 @@ export function isCompressedTextureFormat(format: GPUTextureFormat) {
return format in kCompressedTextureFormatInfo;
}
-export const kFeaturesForFormats = getFeaturesForFormats(kTextureFormats);
+export const kCompatModeUnsupportedStorageTextureFormats: readonly GPUTextureFormat[] = [
+ 'rg32float',
+ 'rg32sint',
+ 'rg32uint',
+] as const;
+
+export function isTextureFormatUsableAsStorageFormat(
+ format: GPUTextureFormat,
+ isCompatibilityMode: boolean
+) {
+ if (isCompatibilityMode) {
+ if (kCompatModeUnsupportedStorageTextureFormats.indexOf(format) >= 0) {
+ return false;
+ }
+ }
+ return !!kTextureFormatInfo[format].color?.storage;
+}
+
+export function isRegularTextureFormat(format: GPUTextureFormat) {
+ return format in kRegularTextureFormatInfo;
+}
+
+export const kFeaturesForFormats = getFeaturesForFormats(kAllTextureFormats);
/**
* Given an array of texture formats return the number of bytes per sample.
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/gpu_test.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/gpu_test.ts
index f9ef1f2f06..fab1844c3c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/gpu_test.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/gpu_test.ts
@@ -9,6 +9,7 @@ import {
TestParams,
} from '../common/framework/fixture.js';
import { globalTestConfig } from '../common/framework/test_config.js';
+import { getGPU } from '../common/util/navigator_gpu.js';
import {
assert,
makeValueTestVariant,
@@ -20,7 +21,13 @@ import {
unreachable,
} from '../common/util/util.js';
-import { getDefaultLimits, kLimits, kQueryTypeInfo } from './capability_info.js';
+import {
+ getDefaultLimits,
+ kLimits,
+ kQueryTypeInfo,
+ WGSLLanguageFeature,
+} from './capability_info.js';
+import { InterpolationType, InterpolationSampling } from './constants.js';
import {
kTextureFormatInfo,
kEncodableTextureFormats,
@@ -29,6 +36,7 @@ import {
EncodableTextureFormat,
isCompressedTextureFormat,
ColorTextureFormat,
+ isTextureFormatUsableAsStorageFormat,
} from './format_info.js';
import { makeBufferWithContents } from './util/buffer.js';
import { checkElementsEqual, checkElementsBetween } from './util/check_contents.js';
@@ -52,7 +60,7 @@ import {
textureContentIsOKByT2B,
} from './util/texture/texture_ok.js';
import { createTextureFromTexelView, createTextureFromTexelViews } from './util/texture.js';
-import { reifyOrigin3D } from './util/unions.js';
+import { reifyExtent3D, reifyOrigin3D } from './util/unions.js';
const devicePool = new DevicePool();
@@ -245,6 +253,56 @@ export class GPUTestSubcaseBatchState extends SubcaseBatchState {
}
}
}
+
+ skipIfTextureFormatNotUsableAsStorageTexture(...formats: (GPUTextureFormat | undefined)[]) {
+ for (const format of formats) {
+ if (format && !isTextureFormatUsableAsStorageFormat(format, this.isCompatibility)) {
+ this.skip(`Texture with ${format} is not usable as a storage texture`);
+ }
+ }
+ }
+
+ /**
+ * Skips test if the given interpolation type or sampling is not supported.
+ */
+ skipIfInterpolationTypeOrSamplingNotSupported({
+ type,
+ sampling,
+ }: {
+ type?: InterpolationType;
+ sampling?: InterpolationSampling;
+ }) {
+ if (this.isCompatibility) {
+ this.skipIf(
+ type === 'linear',
+ 'interpolation type linear is not supported in compatibility mode'
+ );
+ this.skipIf(
+ sampling === 'sample',
+ 'interpolation type linear is not supported in compatibility mode'
+ );
+ }
+ }
+
+ /** Skips this test case if the `langFeature` is *not* supported. */
+ skipIfLanguageFeatureNotSupported(langFeature: WGSLLanguageFeature) {
+ if (!this.hasLanguageFeature(langFeature)) {
+ this.skip(`WGSL language feature '${langFeature}' is not supported`);
+ }
+ }
+
+ /** Skips this test case if the `langFeature` is supported. */
+ skipIfLanguageFeatureSupported(langFeature: WGSLLanguageFeature) {
+ if (this.hasLanguageFeature(langFeature)) {
+ this.skip(`WGSL language feature '${langFeature}' is supported`);
+ }
+ }
+
+ /** returns true iff the `langFeature` is supported */
+ hasLanguageFeature(langFeature: WGSLLanguageFeature) {
+ const lf = getGPU(this.recorder).wgslLanguageFeatures;
+ return lf !== undefined && lf.has(langFeature);
+ }
}
/**
@@ -420,6 +478,26 @@ export class GPUTestBase extends Fixture<GPUTestSubcaseBatchState> {
}
}
+ /** Skips this test case if the `langFeature` is *not* supported. */
+ skipIfLanguageFeatureNotSupported(langFeature: WGSLLanguageFeature) {
+ if (!this.hasLanguageFeature(langFeature)) {
+ this.skip(`WGSL language feature '${langFeature}' is not supported`);
+ }
+ }
+
+ /** Skips this test case if the `langFeature` is supported. */
+ skipIfLanguageFeatureSupported(langFeature: WGSLLanguageFeature) {
+ if (this.hasLanguageFeature(langFeature)) {
+ this.skip(`WGSL language feature '${langFeature}' is supported`);
+ }
+ }
+
+ /** returns true iff the `langFeature` is supported */
+ hasLanguageFeature(langFeature: WGSLLanguageFeature) {
+ const lf = getGPU(this.rec).wgslLanguageFeatures;
+ return lf !== undefined && lf.has(langFeature);
+ }
+
/**
* Expect a GPUBuffer's contents to pass the provided check.
*
@@ -730,7 +808,8 @@ export class GPUTestBase extends Fixture<GPUTestSubcaseBatchState> {
slice = 0,
layout,
generateWarningOnly = false,
- checkElementsBetweenFn = (act, [a, b]) => checkElementsBetween(act, [i => a[i], i => b[i]]),
+ checkElementsBetweenFn = (act, [a, b]) =>
+ checkElementsBetween(act, [i => a[i] as number, i => b[i] as number]),
}: {
exp: [TypedArrayBufferView, TypedArrayBufferView];
slice?: number;
@@ -757,24 +836,32 @@ export class GPUTestBase extends Fixture<GPUTestSubcaseBatchState> {
/**
* Emulate a texture to buffer copy by using a compute shader
- * to load texture value of a single pixel and write to a storage buffer.
- * For sample count == 1, the buffer contains only one value of the sample.
- * For sample count > 1, the buffer contains (N = sampleCount) values sorted
+ * to load texture values of a subregion of a 2d texture and write to a storage buffer.
+ * For sample count == 1, the buffer contains extent[0] * extent[1] of the sample.
+ * For sample count > 1, the buffer contains extent[0] * extent[1] * (N = sampleCount) values sorted
* in the order of their sample index [0, sampleCount - 1]
*
* This can be useful when the texture to buffer copy is not available to the texture format
* e.g. (depth24plus), or when the texture is multisampled.
*
- * MAINTENANCE_TODO: extend to read multiple pixels with given origin and size.
+ * MAINTENANCE_TODO: extend texture dimension to 1d and 3d.
*
* @returns storage buffer containing the copied value from the texture.
*/
- copySinglePixelTextureToBufferUsingComputePass(
+ copy2DTextureToBufferUsingComputePass(
type: ScalarType,
componentCount: number,
textureView: GPUTextureView,
- sampleCount: number
+ sampleCount: number = 1,
+ extent_: GPUExtent3D = [1, 1, 1],
+ origin_: GPUOrigin3D = [0, 0, 0]
): GPUBuffer {
+ const origin = reifyOrigin3D(origin_);
+ const extent = reifyExtent3D(extent_);
+ const width = extent.width;
+ const height = extent.height;
+ const kWorkgroupSizeX = 8;
+ const kWorkgroupSizeY = 8;
const textureSrcCode =
sampleCount === 1
? `@group(0) @binding(0) var src: texture_2d<${type}>;`
@@ -787,13 +874,24 @@ export class GPUTestBase extends Fixture<GPUTestSubcaseBatchState> {
${textureSrcCode}
@group(0) @binding(1) var<storage, read_write> dst : Buffer;
- @compute @workgroup_size(1) fn main() {
- var coord = vec2<i32>(0, 0);
- for (var sampleIndex = 0; sampleIndex < ${sampleCount};
+ struct Params {
+ origin: vec2u,
+ extent: vec2u,
+ };
+ @group(0) @binding(2) var<uniform> params : Params;
+
+ @compute @workgroup_size(${kWorkgroupSizeX}, ${kWorkgroupSizeY}, 1) fn main(@builtin(global_invocation_id) id : vec3u) {
+ let boundary = params.origin + params.extent;
+ let coord = params.origin + id.xy;
+ if (any(coord >= boundary)) {
+ return;
+ }
+ let offset = (id.x + id.y * params.extent.x) * ${componentCount} * ${sampleCount};
+ for (var sampleIndex = 0u; sampleIndex < ${sampleCount};
sampleIndex = sampleIndex + 1) {
- let o = sampleIndex * ${componentCount};
- let v = textureLoad(src, coord, sampleIndex);
- for (var component = 0; component < ${componentCount}; component = component + 1) {
+ let o = offset + sampleIndex * ${componentCount};
+ let v = textureLoad(src, coord.xy, sampleIndex);
+ for (var component = 0u; component < ${componentCount}; component = component + 1) {
dst.data[o + component] = v[component];
}
}
@@ -810,11 +908,16 @@ export class GPUTestBase extends Fixture<GPUTestSubcaseBatchState> {
});
const storageBuffer = this.device.createBuffer({
- size: sampleCount * type.size * componentCount,
+ size: sampleCount * type.size * componentCount * width * height,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
});
this.trackForCleanup(storageBuffer);
+ const uniformBuffer = this.makeBufferWithContents(
+ new Uint32Array([origin.x, origin.y, width, height]),
+ GPUBufferUsage.UNIFORM
+ );
+
const uniformBindGroup = this.device.createBindGroup({
layout: computePipeline.getBindGroupLayout(0),
entries: [
@@ -828,6 +931,12 @@ export class GPUTestBase extends Fixture<GPUTestSubcaseBatchState> {
buffer: storageBuffer,
},
},
+ {
+ binding: 2,
+ resource: {
+ buffer: uniformBuffer,
+ },
+ },
],
});
@@ -835,7 +944,11 @@ export class GPUTestBase extends Fixture<GPUTestSubcaseBatchState> {
const pass = encoder.beginComputePass();
pass.setPipeline(computePipeline);
pass.setBindGroup(0, uniformBindGroup);
- pass.dispatchWorkgroups(1);
+ pass.dispatchWorkgroups(
+ Math.floor((width + kWorkgroupSizeX - 1) / kWorkgroupSizeX),
+ Math.floor((height + kWorkgroupSizeY - 1) / kWorkgroupSizeY),
+ 1
+ );
pass.end();
this.device.queue.submit([encoder.finish()]);
@@ -1081,11 +1194,17 @@ export class GPUTest extends GPUTestBase {
this.mismatchedProvider = await this.sharedState.acquireMismatchedProvider();
}
+ /** GPUAdapter that the device was created from. */
+ get adapter(): GPUAdapter {
+ assert(this.provider !== undefined, 'internal error: DeviceProvider missing');
+ return this.provider.adapter;
+ }
+
/**
* GPUDevice for the test to use.
*/
override get device(): GPUDevice {
- assert(this.provider !== undefined, 'internal error: GPUDevice missing?');
+ assert(this.provider !== undefined, 'internal error: DeviceProvider missing');
return this.provider.device;
}
@@ -1237,8 +1356,10 @@ export interface TextureTestMixinType {
): Generator<Required<GPUOrigin3DDict>>;
}
+type PipelineType = '2d' | '2d-array';
+
type ImageCopyTestResources = {
- pipeline: GPURenderPipeline;
+ pipelineByPipelineType: Map<PipelineType, GPURenderPipeline>;
};
const s_deviceToResourcesMap = new WeakMap<GPUDevice, ImageCopyTestResources>();
@@ -1246,8 +1367,23 @@ const s_deviceToResourcesMap = new WeakMap<GPUDevice, ImageCopyTestResources>();
/**
* Gets a (cached) pipeline to render a texture to an rgba8unorm texture
*/
-function getPipelineToRenderTextureToRGB8UnormTexture(device: GPUDevice) {
+function getPipelineToRenderTextureToRGB8UnormTexture(
+ device: GPUDevice,
+ texture: GPUTexture,
+ isCompatibility: boolean
+) {
if (!s_deviceToResourcesMap.has(device)) {
+ s_deviceToResourcesMap.set(device, {
+ pipelineByPipelineType: new Map<PipelineType, GPURenderPipeline>(),
+ });
+ }
+
+ const { pipelineByPipelineType } = s_deviceToResourcesMap.get(device)!;
+ const pipelineType: PipelineType =
+ isCompatibility && texture.depthOrArrayLayers > 1 ? '2d-array' : '2d';
+ if (!pipelineByPipelineType.get(pipelineType)) {
+ const [textureType, layerCode] =
+ pipelineType === '2d' ? ['texture_2d', ''] : ['texture_2d_array', ', uni.baseArrayLayer'];
const module = device.createShaderModule({
code: `
struct VSOutput {
@@ -1255,6 +1391,10 @@ function getPipelineToRenderTextureToRGB8UnormTexture(device: GPUDevice) {
@location(0) texcoord: vec2f,
};
+ struct Uniforms {
+ baseArrayLayer: u32,
+ };
+
@vertex fn vs(
@builtin(vertex_index) vertexIndex : u32
) -> VSOutput {
@@ -1275,10 +1415,11 @@ function getPipelineToRenderTextureToRGB8UnormTexture(device: GPUDevice) {
}
@group(0) @binding(0) var ourSampler: sampler;
- @group(0) @binding(1) var ourTexture: texture_2d<f32>;
+ @group(0) @binding(1) var ourTexture: ${textureType}<f32>;
+ @group(0) @binding(2) var<uniform> uni: Uniforms;
@fragment fn fs(fsInput: VSOutput) -> @location(0) vec4f {
- return textureSample(ourTexture, ourSampler, fsInput.texcoord);
+ return textureSample(ourTexture, ourSampler, fsInput.texcoord${layerCode});
}
`,
});
@@ -1294,10 +1435,10 @@ function getPipelineToRenderTextureToRGB8UnormTexture(device: GPUDevice) {
targets: [{ format: 'rgba8unorm' }],
},
});
- s_deviceToResourcesMap.set(device, { pipeline });
+ pipelineByPipelineType.set(pipelineType, pipeline);
}
- const { pipeline } = s_deviceToResourcesMap.get(device)!;
- return pipeline;
+ const pipeline = pipelineByPipelineType.get(pipelineType)!;
+ return { pipelineType, pipeline };
}
type LinearCopyParameters = {
@@ -1441,7 +1582,11 @@ export function TextureTestMixin<F extends FixtureClass<GPUTest>>(
// Render every layer of both textures at mipLevel to an rgba8unorm texture
// that matches the size of the mipLevel. After each render, copy the
// result to a buffer and expect the results from both textures to match.
- const pipeline = getPipelineToRenderTextureToRGB8UnormTexture(this.device);
+ const { pipelineType, pipeline } = getPipelineToRenderTextureToRGB8UnormTexture(
+ this.device,
+ actualTexture,
+ this.isCompatibility
+ );
const readbackPromisesPerTexturePerLayer = [actualTexture, expectedTexture].map(
(texture, ndx) => {
const attachmentSize = virtualMipSize('2d', [texture.width, texture.height, 1], mipLevel);
@@ -1457,24 +1602,45 @@ export function TextureTestMixin<F extends FixtureClass<GPUTest>>(
const numLayers = texture.depthOrArrayLayers;
const readbackPromisesPerLayer = [];
+
+ const uniformBuffer = this.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
+ });
+ this.trackForCleanup(uniformBuffer);
+
for (let layer = 0; layer < numLayers; ++layer) {
+ const viewDescriptor: GPUTextureViewDescriptor = {
+ baseMipLevel: mipLevel,
+ mipLevelCount: 1,
+ ...(!this.isCompatibility && {
+ baseArrayLayer: layer,
+ arrayLayerCount: 1,
+ }),
+ dimension: pipelineType,
+ };
+
const bindGroup = this.device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: sampler },
{
binding: 1,
- resource: texture.createView({
- baseMipLevel: mipLevel,
- mipLevelCount: 1,
- baseArrayLayer: layer,
- arrayLayerCount: 1,
- dimension: '2d',
- }),
+ resource: texture.createView(viewDescriptor),
},
+ ...(pipelineType === '2d-array'
+ ? [
+ {
+ binding: 2,
+ resource: { buffer: uniformBuffer },
+ },
+ ]
+ : []),
],
});
+ this.device.queue.writeBuffer(uniformBuffer, 0, new Uint32Array([layer]));
+
const encoder = this.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/idl/constructable.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/idl/constructable.spec.ts
new file mode 100644
index 0000000000..8fd9d08d3c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/idl/constructable.spec.ts
@@ -0,0 +1,54 @@
+export const description = `
+Test that constructable WebGPU objects are actually constructable.
+`;
+
+import { makeTestGroup } from './../../common/framework/test_group.js';
+import { IDLTest } from './idl_test.js';
+
+export const g = makeTestGroup(IDLTest);
+
+g.test('gpu_errors')
+ .desc('tests that GPUErrors are constructable')
+ .params(u =>
+ u.combine('errorType', [
+ 'GPUInternalError',
+ 'GPUOutOfMemoryError',
+ 'GPUValidationError',
+ ] as const)
+ )
+ .fn(t => {
+ const { errorType } = t.params;
+ const Ctor = globalThis[errorType];
+ const msg = 'this is a test';
+ const error = new Ctor(msg);
+ t.expect(error.message === msg);
+ });
+
+const pipelineErrorOptions: GPUPipelineErrorInit[] = [
+ { reason: 'validation' },
+ { reason: 'internal' },
+];
+
+g.test('pipeline_errors')
+ .desc('tests that GPUPipelineError is constructable')
+ .params(u =>
+ u //
+ .combine('msg', [undefined, 'some msg'])
+ .combine('options', pipelineErrorOptions)
+ )
+ .fn(t => {
+ const { msg, options } = t.params;
+ const error = new GPUPipelineError(msg, options);
+ const expectedMsg = msg || '';
+ t.expect(error.message === expectedMsg);
+ t.expect(error.reason === options.reason);
+ });
+
+g.test('uncaptured_error_event')
+ .desc('tests that GPUUncapturedErrorEvent is constructable')
+ .fn(t => {
+ const msg = 'this is a test';
+ const error = new GPUValidationError(msg);
+ const event = new GPUUncapturedErrorEvent('uncapturedError', { error });
+ t.expect(event.error === error);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/listing_meta.json b/dom/webgpu/tests/cts/checkout/src/webgpu/listing_meta.json
index f9caeefc6e..6ee25813f4 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/listing_meta.json
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/listing_meta.json
@@ -1,5 +1,5 @@
{
- "_comment": "SEMI AUTO-GENERATED: Please read docs/adding_timing_metadata.md.",
+ "_comment": "SEMI AUTO-GENERATED. This list is NOT exhaustive. Please read docs/adding_timing_metadata.md.",
"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 },
@@ -9,6 +9,7 @@
"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,out_of_range:*": { "subcaseMS": 1.000 },
"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 },
@@ -93,6 +94,7 @@
"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,readonly_depth_stencil:sampling_while_testing:*": { "subcaseMS": 50.000 },
"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 },
@@ -108,8 +110,11 @@
"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_creation_from_reflection:*": { "subcaseMS": 0.800 },
"webgpu:api,operation,reflection:buffer_reflection_attributes:*": { "subcaseMS": 0.800 },
+ "webgpu:api,operation,reflection:query_set_creation_from_reflection:*": { "subcaseMS": 0.634 },
"webgpu:api,operation,reflection:query_set_reflection_attributes:*": { "subcaseMS": 0.634 },
+ "webgpu:api,operation,reflection:texture_creation_from_reflection:*": { "subcaseMS": 1.829 },
"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 },
@@ -135,6 +140,9 @@
"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,3d_texture_slices:multiple_color_attachments,same_mip_level:*": { "subcaseMS": 69.400 },
+ "webgpu:api,operation,rendering,3d_texture_slices:multiple_color_attachments,same_slice_with_diff_mip_levels:*": { "subcaseMS": 9.800 },
+ "webgpu:api,operation,rendering,3d_texture_slices:one_color_attachment,mip_levels:*": { "subcaseMS": 54.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 },
@@ -194,6 +202,8 @@
"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,storage_texture,read_only:basic:*": { "subcaseMS": 20.000 },
+ "webgpu:api,operation,storage_texture,read_write:basic:*": { "subcaseMS": 5.000 },
"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 },
@@ -264,7 +274,7 @@
"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,query_types:timestamp:*": { "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:check_capability_guarantees:*": { "subcaseMS": 55.901 },
@@ -277,6 +287,8 @@
"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,maxBindGroups:validate,maxBindGroupsPlusVertexBuffers:*": { "subcaseMS": 11.200 },
+ "webgpu:api,validation,capability_checks,limits,maxBindGroupsPlusVertexBuffers:createRenderPipeline,at_over:*": { "subcaseMS": 11.200 },
+ "webgpu:api,validation,capability_checks,limits,maxBindGroupsPlusVertexBuffers:draw,at_over:*": { "subcaseMS": 11.200 },
"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 },
@@ -356,6 +368,7 @@
"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:resource_compatibility:*": { "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 },
@@ -516,7 +529,6 @@
"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 },
@@ -532,6 +544,8 @@
"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:default_bind_group_layouts_never_match,compute_pass:*": { "subcaseMS": 1.734 },
+ "webgpu:api,validation,encoding,programmable,pipeline_bind_group_compat:default_bind_group_layouts_never_match,render_pass:*": { "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 },
@@ -542,9 +556,9 @@
"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,general:writeTimestamp,device_mismatch:*": { "subcaseMS": 0.101 },
+ "webgpu:api,validation,encoding,queries,general:writeTimestamp,invalid_query_set:*": { "subcaseMS": 0.101 },
+ "webgpu:api,validation,encoding,queries,general:writeTimestamp,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 },
@@ -599,6 +613,7 @@
"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,layout_shader_compat:pipeline_layout_shader_exact_match:*": { "subcaseMS": 2.000 },
"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 },
@@ -629,7 +644,7 @@
"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,query_set:timestamps:*": { "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 },
@@ -663,6 +678,10 @@
"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,depthSlice,bound_check:*": { "subcaseMS": 9.400 },
+ "webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,depthSlice,definedness:*": { "subcaseMS": 5.601 },
+ "webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,depthSlice,overlaps,diff_miplevel:*": { "subcaseMS": 3.901 },
+ "webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,depthSlice,overlaps,same_miplevel:*": { "subcaseMS": 6.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 },
@@ -701,6 +720,7 @@
"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_is_color_format:*": { "subcaseMS": 2.000 },
"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 },
@@ -713,6 +733,7 @@
"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:external_texture:*": { "subcaseMS": 35.189 },
"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 },
@@ -730,6 +751,7 @@
"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,resource_compatibility:resource_compatibility:*": { "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 },
@@ -775,8 +797,11 @@
"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:compute_undefined_entry_point_and_extra_stage:*": { "subcaseMS": 17.075 },
"webgpu:api,validation,shader_module,entry_point:fragment:*": { "subcaseMS": 5.865 },
+ "webgpu:api,validation,shader_module,entry_point:fragment_undefined_entry_point_and_extra_stage:*": { "subcaseMS": 16.050 },
"webgpu:api,validation,shader_module,entry_point:vertex:*": { "subcaseMS": 5.803 },
+ "webgpu:api,validation,shader_module,entry_point:vertex_undefined_entry_point_and_extra_stage:*": { "subcaseMS": 15.851 },
"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 },
@@ -815,8 +840,6 @@
"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 },
@@ -828,14 +851,27 @@
"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,createBindGroup:viewDimension_matches_textureBindingViewDimension:*": { "subcaseMS": 6.523 },
+ "webgpu:compat,api,validation,createBindGroupLayout:unsupportedStorageTextureFormats:*": { "subcaseMS": 0.601 },
"webgpu:compat,api,validation,encoding,cmds,copyTextureToBuffer:compressed:*": { "subcaseMS": 202.929 },
+ "webgpu:compat,api,validation,encoding,cmds,copyTextureToTexture:compressed:*": { "subcaseMS": 0.600 },
+ "webgpu:compat,api,validation,encoding,cmds,copyTextureToTexture:multisample:*": { "subcaseMS": 0.600 },
"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,depth_stencil_state:depthBiasClamp:*": { "subcaseMS": 1.604 },
"webgpu:compat,api,validation,render_pipeline,fragment_state:colorState:*": { "subcaseMS": 32.604 },
+ "webgpu:compat,api,validation,render_pipeline,shader_module:interpolate:*": { "subcaseMS": 1.502 },
+ "webgpu:compat,api,validation,render_pipeline,shader_module:sample_index:*": { "subcaseMS": 1.502 },
"webgpu:compat,api,validation,render_pipeline,shader_module:sample_mask:*": { "subcaseMS": 14.801 },
+ "webgpu:compat,api,validation,render_pipeline,shader_module:unsupportedStorageTextureFormats,computePipeline:*": { "subcaseMS": 0.601 },
+ "webgpu:compat,api,validation,render_pipeline,shader_module:unsupportedStorageTextureFormats,renderPipeline:*": { "subcaseMS": 0.601 },
"webgpu:compat,api,validation,render_pipeline,vertex_state:maxVertexAttributesVertexIndexInstanceIndex:*": { "subcaseMS": 3.700 },
+ "webgpu:compat,api,validation,texture,createTexture:depthOrArrayLayers_incompatible_with_textureBindingViewDimension:*": { "subcaseMS": 12.712 },
+ "webgpu:compat,api,validation,texture,createTexture:format_reinterpretation:*": { "subcaseMS": 7.012 },
+ "webgpu:compat,api,validation,texture,createTexture:invalidTextureBindingViewDimension:*": { "subcaseMS": 6.022 },
+ "webgpu:compat,api,validation,texture,createTexture:unsupportedStorageTextureFormats:*": { "subcaseMS": 0.601 },
"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 },
@@ -862,6 +898,30 @@
"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:idl,constructable:gpu_errors:*": { "subcaseMS": 0.101 },
+ "webgpu:idl,constructable:pipeline_errors:*": { "subcaseMS": 0.101 },
+ "webgpu:idl,constructable:uncaptured_error_event:*": { "subcaseMS": 0.101 },
+ "webgpu:shader,execution,expression,access,array,index:abstract_scalar:*": { "subcaseMS": 235.962 },
+ "webgpu:shader,execution,expression,access,array,index:bool:*": { "subcaseMS": 663.038 },
+ "webgpu:shader,execution,expression,access,array,index:concrete_scalar:*": { "subcaseMS": 1439.796 },
+ "webgpu:shader,execution,expression,access,array,index:runtime_sized:*": { "subcaseMS": 830.024 },
+ "webgpu:shader,execution,expression,access,array,index:vector:*": { "subcaseMS": 2137.684 },
+ "webgpu:shader,execution,expression,access,matrix,index:abstract_float_column:*": { "subcaseMS": 14.643 },
+ "webgpu:shader,execution,expression,access,matrix,index:abstract_float_element:*": { "subcaseMS": 587.333 },
+ "webgpu:shader,execution,expression,access,matrix,index:concrete_float_column:*": { "subcaseMS": 4690.055 },
+ "webgpu:shader,execution,expression,access,matrix,index:concrete_float_element:*": { "subcaseMS": 6855.570 },
+ "webgpu:shader,execution,expression,access,structure,index:buffer:*": { "subcaseMS": 325.082 },
+ "webgpu:shader,execution,expression,access,structure,index:buffer_align:*": { "subcaseMS": 84.911 },
+ "webgpu:shader,execution,expression,access,structure,index:buffer_pointer:*": { "subcaseMS": 307.453 },
+ "webgpu:shader,execution,expression,access,structure,index:buffer_size:*": { "subcaseMS": 45.423 },
+ "webgpu:shader,execution,expression,access,structure,index:const:*": { "subcaseMS": 211.459 },
+ "webgpu:shader,execution,expression,access,structure,index:const_nested:*": { "subcaseMS": 214.727 },
+ "webgpu:shader,execution,expression,access,structure,index:let:*": { "subcaseMS": 201.392 },
+ "webgpu:shader,execution,expression,access,structure,index:param:*": { "subcaseMS": 215.826 },
+ "webgpu:shader,execution,expression,access,vector,components:abstract_scalar:*": { "subcaseMS": 533.768 },
+ "webgpu:shader,execution,expression,access,vector,components:concrete_scalar:*": { "subcaseMS": 11636.652 },
+ "webgpu:shader,execution,expression,access,vector,index:abstract_scalar:*": { "subcaseMS": 215.404 },
+ "webgpu:shader,execution,expression,access,vector,index:concrete_scalar:*": { "subcaseMS": 1707.582 },
"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 },
@@ -877,7 +937,12 @@
"webgpu:shader,execution,expression,binary,af_division:vector:*": { "subcaseMS": 237.134 },
"webgpu:shader,execution,expression,binary,af_division:vector_scalar:*": { "subcaseMS": 580.000 },
"webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*": { "subcaseMS": 11169.534 },
+ "webgpu:shader,execution,expression,binary,af_matrix_matrix_multiplication:matrix_matrix:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,af_matrix_scalar_multiplication:matrix_scalar:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,af_matrix_scalar_multiplication:scalar_matrix:*": { "subcaseMS": 0.000 },
"webgpu:shader,execution,expression,binary,af_matrix_subtraction:matrix:*": { "subcaseMS": 14060.956 },
+ "webgpu:shader,execution,expression,binary,af_matrix_vector_multiplication:matrix_vector:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,af_matrix_vector_multiplication:vector_matrix:*": { "subcaseMS": 0.000 },
"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 },
@@ -890,6 +955,27 @@
"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,ai_arithmetic:addition:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:addition_scalar_vector:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:addition_vector_scalar:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:division:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:division_scalar_vector:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:division_vector_scalar:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:multiplication:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:multiplication_scalar_vector:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:multiplication_vector_scalar:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:remainder:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:remainder_scalar_vector:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:remainder_vector_scalar:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:subtraction:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:subtraction_scalar_vector:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_arithmetic:subtraction_vector_scalar:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,binary,ai_comparison:equals:*": { "subcaseMS": 338.609 },
+ "webgpu:shader,execution,expression,binary,ai_comparison:greater_equals:*": { "subcaseMS": 219.452 },
+ "webgpu:shader,execution,expression,binary,ai_comparison:greater_than:*": { "subcaseMS": 232.750 },
+ "webgpu:shader,execution,expression,binary,ai_comparison:less_equals:*": { "subcaseMS": 228.676 },
+ "webgpu:shader,execution,expression,binary,ai_comparison:less_than:*": { "subcaseMS": 245.506 },
+ "webgpu:shader,execution,expression,binary,ai_comparison:not_equals:*": { "subcaseMS": 222.561 },
"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 },
@@ -1066,16 +1152,16 @@
"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": 464.126 },
+ "webgpu:shader,execution,expression,call,builtin,abs:abstract_float:*": { "subcaseMS": 13489.454 },
"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:abstract_float:*": { "subcaseMS": 12032.378 },
"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:abstract_float:*": { "subcaseMS": 12832.129 },
"webgpu:shader,execution,expression,call,builtin,acosh:f16:*": { "subcaseMS": 140.494 },
"webgpu:shader,execution,expression,call,builtin,acosh:f32:*": { "subcaseMS": 12.588 },
"webgpu:shader,execution,expression,call,builtin,all:bool:*": { "subcaseMS": 6.938 },
@@ -1085,19 +1171,19 @@
"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:abstract_float:*": { "subcaseMS": 12414.721 },
"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:abstract_float:*": { "subcaseMS": 13136.027 },
"webgpu:shader,execution,expression,call,builtin,asinh:f16:*": { "subcaseMS": 59.538 },
"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:abstract_float:*": { "subcaseMS": 6683.345 },
"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:abstract_float:*": { "subcaseMS": 12036.687 },
"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:abstract_float:*": { "subcaseMS": 12956.533 },
"webgpu:shader,execution,expression,call,builtin,atanh:f16:*": { "subcaseMS": 81.619 },
"webgpu:shader,execution,expression,call,builtin,atanh:f32:*": { "subcaseMS": 12.332 },
"webgpu:shader,execution,expression,call,builtin,atomics,atomicAdd:add_storage:*": { "subcaseMS": 6.482 },
@@ -1128,6 +1214,14 @@
"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:af_to_f32:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,bitcast:af_to_i32:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,bitcast:af_to_u32:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,bitcast:af_to_vec2f16:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,bitcast:ai_to_f32:*": { "subcaseMS": 960.104 },
+ "webgpu:shader,execution,expression,call,builtin,bitcast:ai_to_i32:*": { "subcaseMS": 741.443 },
+ "webgpu:shader,execution,expression,call,builtin,bitcast:ai_to_u32:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,bitcast:ai_to_vec2h:*": { "subcaseMS": 170.902 },
"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 },
@@ -1141,6 +1235,8 @@
"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:vec2af_to_vec4f16:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,bitcast:vec2ai_to_vec4h:*": { "subcaseMS": 304.357 },
"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 },
@@ -1150,19 +1246,19 @@
"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:abstract_float:*": { "subcaseMS": 15217.441 },
"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": 11800.350 },
+ "webgpu:shader,execution,expression,call,builtin,clamp:abstract_float:*": { "subcaseMS": 121937.540 },
"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:abstract_float:*": { "subcaseMS": 17109.500 },
"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:abstract_float:*": { "subcaseMS": 11033.538 },
"webgpu:shader,execution,expression,call,builtin,cosh:f16:*": { "subcaseMS": 58.475 },
"webgpu:shader,execution,expression,call,builtin,cosh:f32:*": { "subcaseMS": 9.694 },
"webgpu:shader,execution,expression,call,builtin,countLeadingZeros:i32:*": { "subcaseMS": 7.494 },
@@ -1171,16 +1267,19 @@
"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:abstract_float:*": { "subcaseMS": 60020.496 },
"webgpu:shader,execution,expression,call,builtin,cross:f16:*": { "subcaseMS": 115.503 },
"webgpu:shader,execution,expression,call,builtin,cross:f32:*": { "subcaseMS": 664.926 },
- "webgpu:shader,execution,expression,call,builtin,degrees:abstract_float:*": { "subcaseMS": 533.052 },
+ "webgpu:shader,execution,expression,call,builtin,degrees:abstract_float:*": { "subcaseMS": 12611.693 },
"webgpu:shader,execution,expression,call,builtin,degrees:f16:*": { "subcaseMS": 29.308 },
"webgpu:shader,execution,expression,call,builtin,degrees:f32:*": { "subcaseMS": 79.525 },
- "webgpu:shader,execution,expression,call,builtin,determinant:abstract_float:*": { "subcaseMS": 15.306 },
+ "webgpu:shader,execution,expression,call,builtin,determinant:abstract_float:*": { "subcaseMS": 1785.618 },
"webgpu:shader,execution,expression,call,builtin,determinant:f16:*": { "subcaseMS": 37.192 },
"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:abstract_float:*": { "subcaseMS": 10152.221 },
+ "webgpu:shader,execution,expression,call,builtin,distance:abstract_float_vec2:*": { "subcaseMS": 2896.823 },
+ "webgpu:shader,execution,expression,call,builtin,distance:abstract_float_vec3:*": { "subcaseMS": 3191.871 },
+ "webgpu:shader,execution,expression,call,builtin,distance:abstract_float_vec4:*": { "subcaseMS": 3250.958 },
"webgpu:shader,execution,expression,call,builtin,distance:f16:*": { "subcaseMS": 6675.626 },
"webgpu:shader,execution,expression,call,builtin,distance:f16_vec2:*": { "subcaseMS": 78.300 },
"webgpu:shader,execution,expression,call,builtin,distance:f16_vec3:*": { "subcaseMS": 47.925 },
@@ -1189,31 +1288,43 @@
"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,dot4I8Packed:basic:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,execution,expression,call,builtin,dot4U8Packed:basic:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,execution,expression,call,builtin,dot:abstract_float_vec2:*": { "subcaseMS": 3042.966 },
+ "webgpu:shader,execution,expression,call,builtin,dot:abstract_float_vec3:*": { "subcaseMS": 980.205 },
+ "webgpu:shader,execution,expression,call,builtin,dot:abstract_float_vec4:*": { "subcaseMS": 1036.933 },
+ "webgpu:shader,execution,expression,call,builtin,dot:abstract_int_vec2:*": { "subcaseMS": 2570.488 },
+ "webgpu:shader,execution,expression,call,builtin,dot:abstract_int_vec3:*": { "subcaseMS": 1848.038 },
+ "webgpu:shader,execution,expression,call,builtin,dot:abstract_int_vec4:*": { "subcaseMS": 1742.054 },
"webgpu:shader,execution,expression,call,builtin,dot:f16_vec2:*": { "subcaseMS": 981.225 },
"webgpu:shader,execution,expression,call,builtin,dot:f16_vec3:*": { "subcaseMS": 50.350 },
"webgpu:shader,execution,expression,call,builtin,dot:f16_vec4:*": { "subcaseMS": 52.250 },
"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,dot:i32_vec2:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,dot:i32_vec3:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,dot:i32_vec4:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,dot:u32_vec2:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,dot:u32_vec3:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,builtin,dot:u32_vec4:*": { "subcaseMS": 0.000 },
"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:abstract_float:*": { "subcaseMS": 17938.514 },
"webgpu:shader,execution,expression,call,builtin,exp2:f16:*": { "subcaseMS": 79.501 },
"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:abstract_float:*": { "subcaseMS": 18734.085 },
"webgpu:shader,execution,expression,call,builtin,exp:f16:*": { "subcaseMS": 135.363 },
"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": 120.702 },
+ "webgpu:shader,execution,expression,call,builtin,faceForward:abstract_float_vec2:*": { "subcaseMS": 4753.524 },
+ "webgpu:shader,execution,expression,call,builtin,faceForward:abstract_float_vec3:*": { "subcaseMS": 4697.114 },
+ "webgpu:shader,execution,expression,call,builtin,faceForward:abstract_float_vec4:*": { "subcaseMS": 3417.393 },
"webgpu:shader,execution,expression,call,builtin,faceForward:f16_vec2:*": { "subcaseMS": 485.775 },
"webgpu:shader,execution,expression,call,builtin,faceForward:f16_vec3:*": { "subcaseMS": 560.225 },
"webgpu:shader,execution,expression,call,builtin,faceForward:f16_vec4:*": { "subcaseMS": 670.325 },
@@ -1224,15 +1335,23 @@
"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:abstract_float:*": { "subcaseMS": 20061.136 },
"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:abstract_float:*": { "subcaseMS": 148432.980 },
"webgpu:shader,execution,expression,call,builtin,fma:f16:*": { "subcaseMS": 485.857 },
"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:abstract_float:*": { "subcaseMS": 8575.078 },
"webgpu:shader,execution,expression,call,builtin,fract:f16:*": { "subcaseMS": 46.500 },
"webgpu:shader,execution,expression,call,builtin,fract:f32:*": { "subcaseMS": 12.269 },
+ "webgpu:shader,execution,expression,call,builtin,frexp:abstract_float_exp:*": { "subcaseMS": 383.892 },
+ "webgpu:shader,execution,expression,call,builtin,frexp:abstract_float_fract:*": { "subcaseMS": 934.813 },
+ "webgpu:shader,execution,expression,call,builtin,frexp:abstract_float_vec2_exp:*": { "subcaseMS": 805.237 },
+ "webgpu:shader,execution,expression,call,builtin,frexp:abstract_float_vec2_fract:*": { "subcaseMS": 1657.028 },
+ "webgpu:shader,execution,expression,call,builtin,frexp:abstract_float_vec3_exp:*": { "subcaseMS": 1287.254 },
+ "webgpu:shader,execution,expression,call,builtin,frexp:abstract_float_vec3_fract:*": { "subcaseMS": 2943.004 },
+ "webgpu:shader,execution,expression,call,builtin,frexp:abstract_float_vec4_exp:*": { "subcaseMS": 1905.417 },
+ "webgpu:shader,execution,expression,call,builtin,frexp:abstract_float_vec4_fract:*": { "subcaseMS": 4122.700 },
"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 },
@@ -1253,13 +1372,16 @@
"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:abstract_float:*": { "subcaseMS": 19408.045 },
"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": 142.805 },
+ "webgpu:shader,execution,expression,call,builtin,ldexp:abstract_float:*": { "subcaseMS": 87.640 },
"webgpu:shader,execution,expression,call,builtin,ldexp:f16:*": { "subcaseMS": 271.038 },
"webgpu:shader,execution,expression,call,builtin,ldexp:f32:*": { "subcaseMS": 161.250 },
- "webgpu:shader,execution,expression,call,builtin,length:abstract_float:*": { "subcaseMS": 31.303 },
+ "webgpu:shader,execution,expression,call,builtin,length:abstract_float:*": { "subcaseMS": 377.202 },
+ "webgpu:shader,execution,expression,call,builtin,length:abstract_float_vec2:*": { "subcaseMS": 2253.267 },
+ "webgpu:shader,execution,expression,call,builtin,length:abstract_float_vec3:*": { "subcaseMS": 1989.791 },
+ "webgpu:shader,execution,expression,call,builtin,length:abstract_float_vec4:*": { "subcaseMS": 1756.180 },
"webgpu:shader,execution,expression,call,builtin,length:f16:*": { "subcaseMS": 490.450 },
"webgpu:shader,execution,expression,call,builtin,length:f16_vec2:*": { "subcaseMS": 33.551 },
"webgpu:shader,execution,expression,call,builtin,length:f16_vec3:*": { "subcaseMS": 79.301 },
@@ -1268,28 +1390,28 @@
"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:abstract_float:*": { "subcaseMS": 59147.901 },
"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:abstract_float:*": { "subcaseMS": 63419.245 },
"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": 2810.001 },
+ "webgpu:shader,execution,expression,call,builtin,max:abstract_float:*": { "subcaseMS": 31421.439 },
"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": 3054.101 },
+ "webgpu:shader,execution,expression,call,builtin,min:abstract_float:*": { "subcaseMS": 36353.551 },
"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:abstract_float_matching:*": { "subcaseMS": 215.206 },
- "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec2:*": { "subcaseMS": 14.601 },
- "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec3:*": { "subcaseMS": 18.302 },
- "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec4:*": { "subcaseMS": 12.602 },
+ "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_matching:*": { "subcaseMS": 23421.613 },
+ "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec2:*": { "subcaseMS": 8447.128 },
+ "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec3:*": { "subcaseMS": 8537.399 },
+ "webgpu:shader,execution,expression,call,builtin,mix:abstract_float_nonmatching_vec4:*": { "subcaseMS": 11860.514 },
"webgpu:shader,execution,expression,call,builtin,mix:f16_matching:*": { "subcaseMS": 321.700 },
"webgpu:shader,execution,expression,call,builtin,mix:f16_nonmatching_vec2:*": { "subcaseMS": 653.851 },
"webgpu:shader,execution,expression,call,builtin,mix:f16_nonmatching_vec3:*": { "subcaseMS": 832.076 },
@@ -1322,7 +1444,9 @@
"webgpu:shader,execution,expression,call,builtin,modf:f32_vec4_fract:*": { "subcaseMS": 147.876 },
"webgpu:shader,execution,expression,call,builtin,modf:f32_vec4_whole:*": { "subcaseMS": 134.576 },
"webgpu:shader,execution,expression,call,builtin,modf:f32_whole:*": { "subcaseMS": 94.025 },
- "webgpu:shader,execution,expression,call,builtin,normalize:abstract_float:*": { "subcaseMS": 28.508 },
+ "webgpu:shader,execution,expression,call,builtin,normalize:abstract_float_vec2:*": { "subcaseMS": 3621.170 },
+ "webgpu:shader,execution,expression,call,builtin,normalize:abstract_float_vec3:*": { "subcaseMS": 6170.292 },
+ "webgpu:shader,execution,expression,call,builtin,normalize:abstract_float_vec4:*": { "subcaseMS": 7460.960 },
"webgpu:shader,execution,expression,call,builtin,normalize:f16_vec2:*": { "subcaseMS": 635.100 },
"webgpu:shader,execution,expression,call,builtin,normalize:f16_vec3:*": { "subcaseMS": 112.501 },
"webgpu:shader,execution,expression,call,builtin,normalize:f16_vec4:*": { "subcaseMS": 210.526 },
@@ -1334,21 +1458,29 @@
"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,pack4xI8:basic:*": { "subcaseMS": 64.702 },
+ "webgpu:shader,execution,expression,call,builtin,pack4xI8Clamp:basic:*": { "subcaseMS": 92.602 },
+ "webgpu:shader,execution,expression,call,builtin,pack4xU8:basic:*": { "subcaseMS": 166.600 },
+ "webgpu:shader,execution,expression,call,builtin,pack4xU8Clamp:basic:*": { "subcaseMS": 62.802 },
+ "webgpu:shader,execution,expression,call,builtin,pow:abstract_float:*": { "subcaseMS": 30535.000 },
"webgpu:shader,execution,expression,call,builtin,pow:f16:*": { "subcaseMS": 816.063 },
"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": 492.827 },
+ "webgpu:shader,execution,expression,call,builtin,radians:abstract_float:*": { "subcaseMS": 12268.988 },
"webgpu:shader,execution,expression,call,builtin,radians:f16:*": { "subcaseMS": 18.707 },
"webgpu:shader,execution,expression,call,builtin,radians:f32:*": { "subcaseMS": 74.432 },
- "webgpu:shader,execution,expression,call,builtin,reflect:abstract_float:*": { "subcaseMS": 47.108 },
+ "webgpu:shader,execution,expression,call,builtin,reflect:abstract_float_vec2:*": { "subcaseMS": 5636.961 },
+ "webgpu:shader,execution,expression,call,builtin,reflect:abstract_float_vec3:*": { "subcaseMS": 10753.506 },
+ "webgpu:shader,execution,expression,call,builtin,reflect:abstract_float_vec4:*": { "subcaseMS": 13283.920 },
"webgpu:shader,execution,expression,call,builtin,reflect:f16_vec2:*": { "subcaseMS": 76.975 },
"webgpu:shader,execution,expression,call,builtin,reflect:f16_vec3:*": { "subcaseMS": 69.451 },
"webgpu:shader,execution,expression,call,builtin,reflect:f16_vec4:*": { "subcaseMS": 79.826 },
"webgpu:shader,execution,expression,call,builtin,reflect:f32_vec2:*": { "subcaseMS": 1182.226 },
"webgpu:shader,execution,expression,call,builtin,reflect:f32_vec3:*": { "subcaseMS": 56.326 },
"webgpu:shader,execution,expression,call,builtin,reflect:f32_vec4:*": { "subcaseMS": 65.250 },
- "webgpu:shader,execution,expression,call,builtin,refract:abstract_float:*": { "subcaseMS": 114.404 },
+ "webgpu:shader,execution,expression,call,builtin,refract:abstract_float_vec2:*": { "subcaseMS": 2981.912 },
+ "webgpu:shader,execution,expression,call,builtin,refract:abstract_float_vec3:*": { "subcaseMS": 3440.181 },
+ "webgpu:shader,execution,expression,call,builtin,refract:abstract_float_vec4:*": { "subcaseMS": 5284.328 },
"webgpu:shader,execution,expression,call,builtin,refract:f16_vec2:*": { "subcaseMS": 536.225 },
"webgpu:shader,execution,expression,call,builtin,refract:f16_vec3:*": { "subcaseMS": 627.450 },
"webgpu:shader,execution,expression,call,builtin,refract:f16_vec4:*": { "subcaseMS": 699.801 },
@@ -1357,46 +1489,46 @@
"webgpu:shader,execution,expression,call,builtin,refract:f32_vec4:*": { "subcaseMS": 610.150 },
"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:abstract_float:*": { "subcaseMS": 15818.921 },
"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": 527.425 },
+ "webgpu:shader,execution,expression,call,builtin,saturate:abstract_float:*": { "subcaseMS": 15450.768 },
"webgpu:shader,execution,expression,call,builtin,saturate:f16:*": { "subcaseMS": 23.407 },
"webgpu:shader,execution,expression,call,builtin,saturate:f32:*": { "subcaseMS": 116.275 },
"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": 412.925 },
+ "webgpu:shader,execution,expression,call,builtin,sign:abstract_float:*": { "subcaseMS": 17131.997 },
"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:abstract_float:*": { "subcaseMS": 17098.512 },
"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:abstract_float:*": { "subcaseMS": 11721.224 },
"webgpu:shader,execution,expression,call,builtin,sinh:f16:*": { "subcaseMS": 58.288 },
"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:abstract_float:*": { "subcaseMS": 73577.621 },
"webgpu:shader,execution,expression,call,builtin,smoothstep:f16:*": { "subcaseMS": 616.457 },
"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:abstract_float:*": { "subcaseMS": 1545.649 },
"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:abstract_float:*": { "subcaseMS": 92.310 },
"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:abstract_float:*": { "subcaseMS": 17043.428 },
"webgpu:shader,execution,expression,call,builtin,tan:f16:*": { "subcaseMS": 116.157 },
"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:abstract_float:*": { "subcaseMS": 13578.577 },
"webgpu:shader,execution,expression,call,builtin,tanh:f16:*": { "subcaseMS": 75.982 },
"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,textureDimensions:depth:*": { "subcaseMS": 20.801 },
+ "webgpu:shader,execution,expression,call,builtin,textureDimensions:external:*": { "subcaseMS": 1.700 },
+ "webgpu:shader,execution,expression,call,builtin,textureDimensions:sampled_and_multisampled:*": { "subcaseMS": 16.506 },
+ "webgpu:shader,execution,expression,call,builtin,textureDimensions: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 },
@@ -1423,7 +1555,6 @@
"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 },
@@ -1433,19 +1564,14 @@
"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 },
@@ -1467,10 +1593,10 @@
"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": 755.012 },
+ "webgpu:shader,execution,expression,call,builtin,transpose:abstract_float:*": { "subcaseMS": 64537.678 },
"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 },
+ "webgpu:shader,execution,expression,call,builtin,trunc:abstract_float:*": { "subcaseMS": 12197.517 },
"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 },
@@ -1478,12 +1604,54 @@
"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,unpack4xI8:basic:*": { "subcaseMS": 76.901 },
+ "webgpu:shader,execution,expression,call,builtin,unpack4xU8:basic:*": { "subcaseMS": 78.501 },
"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,call,builtin,workgroupUniformLoad:types:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,execution,expression,call,user,ptr_params:array_length:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,user,ptr_params:atomic_ptr_to_element:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,user,ptr_params:mixed_ptr_parameters:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,user,ptr_params:read_full_object:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,user,ptr_params:read_ptr_to_element:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,user,ptr_params:read_ptr_to_member:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,user,ptr_params:write_full_object:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,user,ptr_params:write_ptr_to_element:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,call,user,ptr_params:write_ptr_to_member:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,constructor,non_zero:abstract_array_elements:*": { "subcaseMS": 115.862 },
+ "webgpu:shader,execution,expression,constructor,non_zero:abstract_matrix_column_vectors:*": { "subcaseMS": 3363.820 },
+ "webgpu:shader,execution,expression,constructor,non_zero:abstract_matrix_elements:*": { "subcaseMS": 35.110 },
+ "webgpu:shader,execution,expression,constructor,non_zero:abstract_vector_elements:*": { "subcaseMS": 52.760 },
+ "webgpu:shader,execution,expression,constructor,non_zero:abstract_vector_mix:*": { "subcaseMS": 29.135 },
+ "webgpu:shader,execution,expression,constructor,non_zero:abstract_vector_splat:*": { "subcaseMS": 70.635 },
+ "webgpu:shader,execution,expression,constructor,non_zero:concrete_array_elements:*": { "subcaseMS": 1578.242 },
+ "webgpu:shader,execution,expression,constructor,non_zero:concrete_matrix_column_vectors:*": { "subcaseMS": 1125.607 },
+ "webgpu:shader,execution,expression,constructor,non_zero:concrete_matrix_elements:*": { "subcaseMS": 2352.444 },
+ "webgpu:shader,execution,expression,constructor,non_zero:concrete_vector_elements:*": { "subcaseMS": 2697.119 },
+ "webgpu:shader,execution,expression,constructor,non_zero:concrete_vector_mix:*": { "subcaseMS": 4056.031 },
+ "webgpu:shader,execution,expression,constructor,non_zero:concrete_vector_splat:*": { "subcaseMS": 10222.094 },
+ "webgpu:shader,execution,expression,constructor,non_zero:matrix_identity:*": { "subcaseMS": 1137.176 },
+ "webgpu:shader,execution,expression,constructor,non_zero:scalar_identity:*": { "subcaseMS": 3153.723 },
+ "webgpu:shader,execution,expression,constructor,non_zero:structure:*": { "subcaseMS": 0.303 },
+ "webgpu:shader,execution,expression,constructor,non_zero:vector_identity:*": { "subcaseMS": 2274.048 },
+ "webgpu:shader,execution,expression,constructor,zero_value:array:*": { "subcaseMS": 500.400 },
+ "webgpu:shader,execution,expression,constructor,zero_value:matrix:*": { "subcaseMS": 205.881 },
+ "webgpu:shader,execution,expression,constructor,zero_value:scalar:*": { "subcaseMS": 39.484 },
+ "webgpu:shader,execution,expression,constructor,zero_value:structure:*": { "subcaseMS": 0.650 },
+ "webgpu:shader,execution,expression,constructor,zero_value:vector:*": { "subcaseMS": 159.975 },
+ "webgpu:shader,execution,expression,precedence:precedence:*": { "subcaseMS": 531.048 },
+ "webgpu:shader,execution,expression,unary,address_of_and_indirection:deref:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,unary,address_of_and_indirection:deref_index:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,unary,address_of_and_indirection:deref_member:*": { "subcaseMS": 0.000 },
"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,ai_arithmetic:negation:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,unary,ai_assignment:abstract:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,unary,ai_assignment:i32:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,unary,ai_assignment:u32:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,expression,unary,ai_complement:complement:*": { "subcaseMS": 0.000 },
"webgpu:shader,execution,expression,unary,bool_conversion:bool:*": { "subcaseMS": 8.357 },
"webgpu:shader,execution,expression,unary,bool_conversion:f16:*": { "subcaseMS": 44.794 },
"webgpu:shader,execution,expression,unary,bool_conversion:f32:*": { "subcaseMS": 41.276 },
@@ -1491,6 +1659,9 @@
"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": 117.604 },
+ "webgpu:shader,execution,expression,unary,f16_conversion:abstract_float:*": { "subcaseMS": 416.757 },
+ "webgpu:shader,execution,expression,unary,f16_conversion:abstract_float_mat:*": { "subcaseMS": 1142.700 },
+ "webgpu:shader,execution,expression,unary,f16_conversion:abstract_int:*": { "subcaseMS": 50.186 },
"webgpu:shader,execution,expression,unary,f16_conversion:bool:*": { "subcaseMS": 34.694 },
"webgpu:shader,execution,expression,unary,f16_conversion:f16:*": { "subcaseMS": 36.013 },
"webgpu:shader,execution,expression,unary,f16_conversion:f16_mat:*": { "subcaseMS": 47.109 },
@@ -1508,12 +1679,15 @@
"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:abstract_float:*": { "subcaseMS": 4.333 },
+ "webgpu:shader,execution,expression,unary,i32_conversion:abstract_int:*": { "subcaseMS": 167.273 },
"webgpu:shader,execution,expression,unary,i32_conversion:bool:*": { "subcaseMS": 6.457 },
"webgpu:shader,execution,expression,unary,i32_conversion:f16:*": { "subcaseMS": 44.363 },
"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_float:*": { "subcaseMS": 68.918 },
"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": 34.251 },
@@ -1521,6 +1695,10 @@
"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:arg_eval:*": { "subcaseMS": 40.386 },
+ "webgpu:shader,execution,flow_control,call:arg_eval_logical_and:*": { "subcaseMS": 43.760 },
+ "webgpu:shader,execution,flow_control,call:arg_eval_logical_or:*": { "subcaseMS": 48.924 },
+ "webgpu:shader,execution,flow_control,call:arg_eval_pointers:*": { "subcaseMS": 147.050 },
"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 },
@@ -1570,6 +1748,8 @@
"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:for_logical_and_condition:*": { "subcaseMS": 46.477 },
+ "webgpu:shader,execution,flow_control,for:for_logical_or_condition:*": { "subcaseMS": 48.615 },
"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 },
@@ -1577,6 +1757,8 @@
"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_break_if_logical_and_condition:*": { "subcaseMS": 6.827 },
+ "webgpu:shader,execution,flow_control,loop:loop_break_if_logical_or_condition:*": { "subcaseMS": 5.846 },
"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 },
@@ -1591,13 +1773,18 @@
"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_inside_loop_with_continue:*": { "subcaseMS": 0.000 },
"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_logical_and_condition:*": { "subcaseMS": 55.574 },
+ "webgpu:shader,execution,flow_control,while:while_logical_or_condition:*": { "subcaseMS": 49.961 },
"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_layout:read_layout:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,execution,memory_layout:write_layout:*": { "subcaseMS": 0.000 },
"webgpu:shader,execution,memory_model,adjacent:f16:*": { "subcaseMS": 23.625 },
"webgpu:shader,execution,memory_model,atomicity:atomicity:*": { "subcaseMS": 77.201 },
"webgpu:shader,execution,memory_model,barrier:workgroup_barrier_load_store:*": { "subcaseMS": 65.850 },
@@ -1608,6 +1795,7 @@
"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,texture_intra_invocation_coherence:texture_intra_invocation_coherence:*": { "subcaseMS": 0.000 },
"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 },
@@ -1625,9 +1813,17 @@
"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,fragment_builtins:inputs,front_facing:*": { "subcaseMS": 1.001 },
+ "webgpu:shader,execution,shader_io,fragment_builtins:inputs,interStage,centroid:*": { "subcaseMS": 1.001 },
+ "webgpu:shader,execution,shader_io,fragment_builtins:inputs,interStage:*": { "subcaseMS": 1.001 },
+ "webgpu:shader,execution,shader_io,fragment_builtins:inputs,position:*": { "subcaseMS": 1.001 },
+ "webgpu:shader,execution,shader_io,fragment_builtins:inputs,sample_index:*": { "subcaseMS": 1.001 },
+ "webgpu:shader,execution,shader_io,fragment_builtins:inputs,sample_mask:*": { "subcaseMS": 1.001 },
"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,shader_io,user_io:passthrough:*": { "subcaseMS": 373.385 },
+ "webgpu:shader,execution,shader_io,workgroup_size:workgroup_size:*": { "subcaseMS": 0.000 },
"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 },
@@ -1635,6 +1831,15 @@
"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,stage:basic_compute:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,execution,stage:basic_render:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,execution,statement,compound:decl:*": { "subcaseMS": 29.767 },
+ "webgpu:shader,execution,statement,discard:all:*": { "subcaseMS": 36.094 },
+ "webgpu:shader,execution,statement,discard:derivatives:*": { "subcaseMS": 15.287 },
+ "webgpu:shader,execution,statement,discard:function_call:*": { "subcaseMS": 11.744 },
+ "webgpu:shader,execution,statement,discard:loop:*": { "subcaseMS": 11.821 },
+ "webgpu:shader,execution,statement,discard:three_quarters:*": { "subcaseMS": 34.735 },
+ "webgpu:shader,execution,statement,discard:uniform_read_loop:*": { "subcaseMS": 13.095 },
"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 },
@@ -1658,12 +1863,33 @@
"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,compound_statement:decl_conflict:*": { "subcaseMS": 5.225 },
+ "webgpu:shader,validation,decl,compound_statement:decl_use:*": { "subcaseMS": 0.625 },
+ "webgpu:shader,validation,decl,const:function_scope:*": { "subcaseMS": 2.088 },
+ "webgpu:shader,validation,decl,const:initializer:*": { "subcaseMS": 0.768 },
"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,const:type:*": { "subcaseMS": 10.651 },
+ "webgpu:shader,validation,decl,context_dependent_resolution:attribute_names:*": { "subcaseMS": 533.132 },
+ "webgpu:shader,validation,decl,context_dependent_resolution:builtin_value_names:*": { "subcaseMS": 25.538 },
+ "webgpu:shader,validation,decl,context_dependent_resolution:diagnostic_rule_names:*": { "subcaseMS": 6.860 },
+ "webgpu:shader,validation,decl,context_dependent_resolution:diagnostic_severity_names:*": { "subcaseMS": 6.252 },
+ "webgpu:shader,validation,decl,context_dependent_resolution:enable_names:*": { "subcaseMS": 2.226 },
+ "webgpu:shader,validation,decl,context_dependent_resolution:interpolation_sampling_names:*": { "subcaseMS": 4.971 },
+ "webgpu:shader,validation,decl,context_dependent_resolution:interpolation_type_names:*": { "subcaseMS": 4.687 },
+ "webgpu:shader,validation,decl,context_dependent_resolution:language_names:*": { "subcaseMS": 4.920 },
+ "webgpu:shader,validation,decl,context_dependent_resolution:swizzle_names:*": { "subcaseMS": 27.579 },
+ "webgpu:shader,validation,decl,let:initializer:*": { "subcaseMS": 0.706 },
+ "webgpu:shader,validation,decl,let:module_scope:*": { "subcaseMS": 0.619 },
+ "webgpu:shader,validation,decl,let:type:*": { "subcaseMS": 122.199 },
+ "webgpu:shader,validation,decl,override:function_scope:*": { "subcaseMS": 1.003 },
+ "webgpu:shader,validation,decl,override:id:*": { "subcaseMS": 69.432 },
+ "webgpu:shader,validation,decl,override:initializer:*": { "subcaseMS": 4.810 },
"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,override:type:*": { "subcaseMS": 12.518 },
"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 },
@@ -1671,32 +1897,76 @@
"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:binding_collision_unused_helper:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:binding_collisions:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:binding_point_on_function_var:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:binding_point_on_non_resources:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:binding_point_on_resources:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:function_addrspace_at_module_scope:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:function_scope_types:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:handle_initializer:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:initializer_kind:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:module_scope_initializers:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,decl,var:module_scope_types:*": { "subcaseMS": 1.000 },
"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,add_sub_mul:invalid_type_with_itself:*": { "subcaseMS": 68.949 },
+ "webgpu:shader,validation,expression,binary,add_sub_mul:scalar_vector:*": { "subcaseMS": 1811.699 },
+ "webgpu:shader,validation,expression,binary,add_sub_mul:scalar_vector_out_of_range:*": { "subcaseMS": 0.719 },
+ "webgpu:shader,validation,expression,binary,and_or_xor:invalid_types:*": { "subcaseMS": 24.069 },
+ "webgpu:shader,validation,expression,binary,and_or_xor:scalar_vector:*": { "subcaseMS": 666.807 },
+ "webgpu:shader,validation,expression,binary,bitwise_shift:invalid_types:*": { "subcaseMS": 22.058 },
+ "webgpu:shader,validation,expression,binary,bitwise_shift:scalar_vector:*": { "subcaseMS": 525.052 },
"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,binary,comparison:invalid_types:*": { "subcaseMS": 39.526 },
+ "webgpu:shader,validation,expression,binary,comparison:scalar_vector:*": { "subcaseMS": 1598.064 },
+ "webgpu:shader,validation,expression,binary,div_rem:invalid_type_with_itself:*": { "subcaseMS": 38.059 },
+ "webgpu:shader,validation,expression,binary,div_rem:scalar_vector:*": { "subcaseMS": 743.721 },
+ "webgpu:shader,validation,expression,binary,div_rem:scalar_vector_out_of_range:*": { "subcaseMS": 650.727 },
+ "webgpu:shader,validation,expression,call,builtin,abs:parameters:*": { "subcaseMS": 10.133 },
"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:parameters:*": { "subcaseMS": 44.578 },
"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:parameters:*": { "subcaseMS": 152.403 },
"webgpu:shader,validation,expression,call,builtin,acosh:values:*": { "subcaseMS": 0.217 },
+ "webgpu:shader,validation,expression,call,builtin,all:argument_types:*": { "subcaseMS": 58740.580 },
+ "webgpu:shader,validation,expression,call,builtin,all:arguments:*": { "subcaseMS": 80483.389 },
+ "webgpu:shader,validation,expression,call,builtin,all:must_use:*": { "subcaseMS": 7564.378 },
+ "webgpu:shader,validation,expression,call,builtin,any:argument_types:*": { "subcaseMS": 160136.896 },
+ "webgpu:shader,validation,expression,call,builtin,any:arguments:*": { "subcaseMS": 50268.983 },
+ "webgpu:shader,validation,expression,call,builtin,any:must_use:*": { "subcaseMS": 7467.652 },
+ "webgpu:shader,validation,expression,call,builtin,arrayLength:access_mode:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,expression,call,builtin,arrayLength:bool_type:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,expression,call,builtin,arrayLength:type:*": { "subcaseMS": 0.000 },
"webgpu:shader,validation,expression,call,builtin,asin:integer_argument:*": { "subcaseMS": 0.878 },
+ "webgpu:shader,validation,expression,call,builtin,asin:parameters:*": { "subcaseMS": 20072.777 },
"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:parameters:*": { "subcaseMS": 17189.159 },
"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:invalid_argument_x:*": { "subcaseMS": 6011.564 },
+ "webgpu:shader,validation,expression,call,builtin,atan2:invalid_argument_y:*": { "subcaseMS": 24242.032 },
+ "webgpu:shader,validation,expression,call,builtin,atan2:must_use:*": { "subcaseMS": 242.807 },
+ "webgpu:shader,validation,expression,call,builtin,atan2:parameters:*": { "subcaseMS": 1360.903 },
"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:parameters:*": { "subcaseMS": 14928.226 },
"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:parameters:*": { "subcaseMS": 19071.799 },
"webgpu:shader,validation,expression,call,builtin,atanh:values:*": { "subcaseMS": 0.231 },
+ "webgpu:shader,validation,expression,call,builtin,atomics:atomic_parameterization:*": { "subcaseMS": 1.346 },
+ "webgpu:shader,validation,expression,call,builtin,atomics:data_parameters:*": { "subcaseMS": 38.382 },
+ "webgpu:shader,validation,expression,call,builtin,atomics:return_types:*": { "subcaseMS": 28.021 },
"webgpu:shader,validation,expression,call,builtin,atomics:stage:*": { "subcaseMS": 1.346 },
+ "webgpu:shader,validation,expression,call,builtin,barriers:no_return_value:*": { "subcaseMS": 1.500 },
+ "webgpu:shader,validation,expression,call,builtin,barriers:only_in_compute:*": { "subcaseMS": 1.500 },
"webgpu:shader,validation,expression,call,builtin,bitcast:bad_const_to_f16:*": { "subcaseMS": 0.753 },
"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 },
@@ -1708,52 +1978,279 @@
"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:args:*": { "subcaseMS": 4.445 },
"webgpu:shader,validation,expression,call,builtin,cos:integer_argument:*": { "subcaseMS": 1.601 },
+ "webgpu:shader,validation,expression,call,builtin,cos:must_use:*": { "subcaseMS": 0.526 },
"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:args:*": { "subcaseMS": 41.832 },
+ "webgpu:shader,validation,expression,call,builtin,cosh:must_use:*": { "subcaseMS": 5.658 },
"webgpu:shader,validation,expression,call,builtin,cosh:values:*": { "subcaseMS": 0.272 },
+ "webgpu:shader,validation,expression,call,builtin,countLeadingZeros:arguments:*": { "subcaseMS": 77.173 },
+ "webgpu:shader,validation,expression,call,builtin,countLeadingZeros:float_argument:*": { "subcaseMS": 64.191 },
+ "webgpu:shader,validation,expression,call,builtin,countLeadingZeros:must_use:*": { "subcaseMS": 4.120 },
+ "webgpu:shader,validation,expression,call,builtin,countLeadingZeros:values:*": { "subcaseMS": 3153.457 },
+ "webgpu:shader,validation,expression,call,builtin,countOneBits:arguments:*": { "subcaseMS": 66.449 },
+ "webgpu:shader,validation,expression,call,builtin,countOneBits:float_argument:*": { "subcaseMS": 44.219 },
+ "webgpu:shader,validation,expression,call,builtin,countOneBits:must_use:*": { "subcaseMS": 3.284 },
+ "webgpu:shader,validation,expression,call,builtin,countOneBits:values:*": { "subcaseMS": 3771.859 },
+ "webgpu:shader,validation,expression,call,builtin,countTrailingZeros:arguments:*": { "subcaseMS": 70.424 },
+ "webgpu:shader,validation,expression,call,builtin,countTrailingZeros:float_argument:*": { "subcaseMS": 46.181 },
+ "webgpu:shader,validation,expression,call,builtin,countTrailingZeros:must_use:*": { "subcaseMS": 3.934 },
+ "webgpu:shader,validation,expression,call,builtin,countTrailingZeros:values:*": { "subcaseMS": 3125.847 },
+ "webgpu:shader,validation,expression,call,builtin,cross:args:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,cross:must_use:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,cross:values:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,degrees:args:*": { "subcaseMS": 4.949 },
"webgpu:shader,validation,expression,call,builtin,degrees:integer_argument:*": { "subcaseMS": 1.311 },
+ "webgpu:shader,validation,expression,call,builtin,degrees:must_use:*": { "subcaseMS": 1.406 },
"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,derivatives:invalid_argument_types:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,derivatives:only_in_fragment:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,determinant:args:*": { "subcaseMS": 15.538 },
+ "webgpu:shader,validation,expression,call,builtin,determinant:matrix_args:*": { "subcaseMS": 193.897 },
+ "webgpu:shader,validation,expression,call,builtin,determinant:must_use:*": { "subcaseMS": 2.856 },
+ "webgpu:shader,validation,expression,call,builtin,distance:args:*": { "subcaseMS": 84.655 },
+ "webgpu:shader,validation,expression,call,builtin,distance:must_use:*": { "subcaseMS": 6.757 },
+ "webgpu:shader,validation,expression,call,builtin,distance:values:*": { "subcaseMS": 9849.553 },
+ "webgpu:shader,validation,expression,call,builtin,dot4I8Packed:args:*": { "subcaseMS": 48.785 },
+ "webgpu:shader,validation,expression,call,builtin,dot4I8Packed:must_use:*": { "subcaseMS": 0.300 },
+ "webgpu:shader,validation,expression,call,builtin,dot4I8Packed:supported:*": { "subcaseMS": 1.100 },
+ "webgpu:shader,validation,expression,call,builtin,dot4I8Packed:unsupported:*": { "subcaseMS": 7.250 },
+ "webgpu:shader,validation,expression,call,builtin,dot4U8Packed:args:*": { "subcaseMS": 45.347 },
+ "webgpu:shader,validation,expression,call,builtin,dot4U8Packed:must_use:*": { "subcaseMS": 0.100 },
+ "webgpu:shader,validation,expression,call,builtin,dot4U8Packed:supported:*": { "subcaseMS": 0.401 },
+ "webgpu:shader,validation,expression,call,builtin,dot4U8Packed:unsupported:*": { "subcaseMS": 3.200 },
+ "webgpu:shader,validation,expression,call,builtin,exp2:args:*": { "subcaseMS": 45.200 },
+ "webgpu:shader,validation,expression,call,builtin,exp2:must_use:*": { "subcaseMS": 6.346 },
"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:args:*": { "subcaseMS": 44.946 },
+ "webgpu:shader,validation,expression,call,builtin,exp:must_use:*": { "subcaseMS": 6.140 },
"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,extractBits:count_offset:*": { "subcaseMS": 131.573 },
+ "webgpu:shader,validation,expression,call,builtin,extractBits:must_use:*": { "subcaseMS": 4.630 },
+ "webgpu:shader,validation,expression,call,builtin,extractBits:typed_arguments:*": { "subcaseMS": 7928.233 },
+ "webgpu:shader,validation,expression,call,builtin,extractBits:values:*": { "subcaseMS": 28802.248 },
+ "webgpu:shader,validation,expression,call,builtin,faceForward:args:*": { "subcaseMS": 105.517 },
+ "webgpu:shader,validation,expression,call,builtin,faceForward:must_use:*": { "subcaseMS": 5.412 },
+ "webgpu:shader,validation,expression,call,builtin,faceForward:values:*": { "subcaseMS": 96662.064 },
+ "webgpu:shader,validation,expression,call,builtin,firstLeadingBit:arguments:*": { "subcaseMS": 210.892 },
+ "webgpu:shader,validation,expression,call,builtin,firstLeadingBit:float_argument:*": { "subcaseMS": 29.714 },
+ "webgpu:shader,validation,expression,call,builtin,firstLeadingBit:must_use:*": { "subcaseMS": 2.214 },
+ "webgpu:shader,validation,expression,call,builtin,firstLeadingBit:values:*": { "subcaseMS": 4227.758 },
+ "webgpu:shader,validation,expression,call,builtin,firstTrailingBit:arguments:*": { "subcaseMS": 65.356 },
+ "webgpu:shader,validation,expression,call,builtin,firstTrailingBit:float_argument:*": { "subcaseMS": 41.249 },
+ "webgpu:shader,validation,expression,call,builtin,firstTrailingBit:must_use:*": { "subcaseMS": 1.982 },
+ "webgpu:shader,validation,expression,call,builtin,firstTrailingBit:values:*": { "subcaseMS": 4203.191 },
+ "webgpu:shader,validation,expression,call,builtin,floor:args:*": { "subcaseMS": 4.221 },
+ "webgpu:shader,validation,expression,call,builtin,floor:integer_argument:*": { "subcaseMS": 48.400 },
+ "webgpu:shader,validation,expression,call,builtin,floor:must_use:*": { "subcaseMS": 0.170 },
+ "webgpu:shader,validation,expression,call,builtin,floor:values:*": { "subcaseMS": 29.668 },
+ "webgpu:shader,validation,expression,call,builtin,fract:args:*": { "subcaseMS": 45.422 },
+ "webgpu:shader,validation,expression,call,builtin,fract:must_use:*": { "subcaseMS": 5.547 },
+ "webgpu:shader,validation,expression,call,builtin,fract:values:*": { "subcaseMS": 4441.607 },
+ "webgpu:shader,validation,expression,call,builtin,frexp:args:*": { "subcaseMS": 43.518 },
+ "webgpu:shader,validation,expression,call,builtin,frexp:must_use:*": { "subcaseMS": 6.130 },
+ "webgpu:shader,validation,expression,call,builtin,frexp:values:*": { "subcaseMS": 4741.981 },
+ "webgpu:shader,validation,expression,call,builtin,insertBits:count_offset:*": { "subcaseMS": 11904.035 },
+ "webgpu:shader,validation,expression,call,builtin,insertBits:mismatched:*": { "subcaseMS": 659.718 },
+ "webgpu:shader,validation,expression,call,builtin,insertBits:must_use:*": { "subcaseMS": 4.243 },
+ "webgpu:shader,validation,expression,call,builtin,insertBits:typed_arguments:*": { "subcaseMS": 3025.440 },
+ "webgpu:shader,validation,expression,call,builtin,insertBits:values:*": { "subcaseMS": 98566.796 },
+ "webgpu:shader,validation,expression,call,builtin,inverseSqrt:args:*": { "subcaseMS": 41.941 },
+ "webgpu:shader,validation,expression,call,builtin,inverseSqrt:must_use:*": { "subcaseMS": 5.614 },
"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:args:*": { "subcaseMS": 4.528 },
"webgpu:shader,validation,expression,call,builtin,log2:integer_argument:*": { "subcaseMS": 1.034 },
+ "webgpu:shader,validation,expression,call,builtin,log2:must_use:*": { "subcaseMS": 1.149 },
"webgpu:shader,validation,expression,call,builtin,log2:values:*": { "subcaseMS": 0.398 },
+ "webgpu:shader,validation,expression,call,builtin,log:args:*": { "subcaseMS": 5.197 },
"webgpu:shader,validation,expression,call,builtin,log:integer_argument:*": { "subcaseMS": 1.134 },
+ "webgpu:shader,validation,expression,call,builtin,log:must_use:*": { "subcaseMS": 1597.590 },
"webgpu:shader,validation,expression,call,builtin,log:values:*": { "subcaseMS": 0.291 },
+ "webgpu:shader,validation,expression,call,builtin,max:args:*": { "subcaseMS": 77.529 },
+ "webgpu:shader,validation,expression,call,builtin,max:must_use:*": { "subcaseMS": 8.286 },
+ "webgpu:shader,validation,expression,call,builtin,max:values:*": { "subcaseMS": 260241.074 },
+ "webgpu:shader,validation,expression,call,builtin,min:args:*": { "subcaseMS": 67.260 },
+ "webgpu:shader,validation,expression,call,builtin,min:must_use:*": { "subcaseMS": 3.899 },
+ "webgpu:shader,validation,expression,call,builtin,min:values:*": { "subcaseMS": 246733.594 },
"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,normalize:args:*": { "subcaseMS": 25.416 },
+ "webgpu:shader,validation,expression,call,builtin,normalize:invalid_argument:*": { "subcaseMS": 3.258 },
+ "webgpu:shader,validation,expression,call,builtin,normalize:must_use:*": { "subcaseMS": 5.626 },
+ "webgpu:shader,validation,expression,call,builtin,normalize:values:*": { "subcaseMS": 3241.079 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xI8:args:*": { "subcaseMS": 36.226 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xI8:must_use:*": { "subcaseMS": 6.500 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xI8:supported:*": { "subcaseMS": 113.501 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xI8:unsupported:*": { "subcaseMS": 739.400 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xI8Clamp:args:*": { "subcaseMS": 21.994 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xI8Clamp:must_use:*": { "subcaseMS": 34.301 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xI8Clamp:supported:*": { "subcaseMS": 100.450 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xI8Clamp:unsupported:*": { "subcaseMS": 751.101 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xU8:args:*": { "subcaseMS": 24.783 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xU8:must_use:*": { "subcaseMS": 5.300 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xU8:supported:*": { "subcaseMS": 449.800 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xU8:unsupported:*": { "subcaseMS": 773.702 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xU8Clamp:args:*": { "subcaseMS": 26.118 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xU8Clamp:must_use:*": { "subcaseMS": 32.600 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xU8Clamp:supported:*": { "subcaseMS": 134.750 },
+ "webgpu:shader,validation,expression,call,builtin,pack4xU8Clamp:unsupported:*": { "subcaseMS": 570.500 },
+ "webgpu:shader,validation,expression,call,builtin,quantizeToF16:args:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,quantizeToF16:must_use:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,quantizeToF16:values:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,radians:args:*": { "subcaseMS": 4.561 },
"webgpu:shader,validation,expression,call,builtin,radians:integer_argument:*": { "subcaseMS": 1.811 },
+ "webgpu:shader,validation,expression,call,builtin,radians:must_use:*": { "subcaseMS": 0.757 },
"webgpu:shader,validation,expression,call,builtin,radians:values:*": { "subcaseMS": 0.382 },
+ "webgpu:shader,validation,expression,call,builtin,reflect:args:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,reflect:must_use:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,reflect:values:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,reverseBits:arguments:*": { "subcaseMS": 77.380 },
+ "webgpu:shader,validation,expression,call,builtin,reverseBits:float_argument:*": { "subcaseMS": 28.806 },
+ "webgpu:shader,validation,expression,call,builtin,reverseBits:must_use:*": { "subcaseMS": 2.063 },
+ "webgpu:shader,validation,expression,call,builtin,reverseBits:values:*": { "subcaseMS": 3140.778 },
"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,select:argument_types_1_and_2:*": { "subcaseMS": 101642.926 },
+ "webgpu:shader,validation,expression,call,builtin,select:argument_types_3:*": { "subcaseMS": 148.474 },
+ "webgpu:shader,validation,expression,call,builtin,select:arguments:*": { "subcaseMS": 66398.067 },
+ "webgpu:shader,validation,expression,call,builtin,select:must_use:*": { "subcaseMS": 4.312 },
+ "webgpu:shader,validation,expression,call,builtin,sign:args:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,sign:must_use:*": { "subcaseMS": 1.000 },
"webgpu:shader,validation,expression,call,builtin,sign:values:*": { "subcaseMS": 0.343 },
+ "webgpu:shader,validation,expression,call,builtin,sin:args:*": { "subcaseMS": 4.443 },
"webgpu:shader,validation,expression,call,builtin,sin:integer_argument:*": { "subcaseMS": 1.189 },
+ "webgpu:shader,validation,expression,call,builtin,sin:must_use:*": { "subcaseMS": 0.588 },
"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:args:*": { "subcaseMS": 42.542 },
+ "webgpu:shader,validation,expression,call,builtin,sinh:must_use:*": { "subcaseMS": 5.980 },
"webgpu:shader,validation,expression,call,builtin,sinh:values:*": { "subcaseMS": 0.357 },
+ "webgpu:shader,validation,expression,call,builtin,smoothstep:argument_types:*": { "subcaseMS": 69163.359 },
+ "webgpu:shader,validation,expression,call,builtin,smoothstep:arguments:*": { "subcaseMS": 131.134 },
+ "webgpu:shader,validation,expression,call,builtin,smoothstep:values:*": { "subcaseMS": 81643.500 },
+ "webgpu:shader,validation,expression,call,builtin,sqrt:args:*": { "subcaseMS": 5.398 },
"webgpu:shader,validation,expression,call,builtin,sqrt:integer_argument:*": { "subcaseMS": 1.356 },
+ "webgpu:shader,validation,expression,call,builtin,sqrt:must_use:*": { "subcaseMS": 1.286 },
"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,step:args:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,step:must_use:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,step:values:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,tan:args:*": { "subcaseMS": 43.560 },
+ "webgpu:shader,validation,expression,call,builtin,tan:must_use:*": { "subcaseMS": 5.401 },
"webgpu:shader,validation,expression,call,builtin,tan:values:*": { "subcaseMS": 0.350 },
+ "webgpu:shader,validation,expression,call,builtin,tanh:args:*": { "subcaseMS": 43.571 },
+ "webgpu:shader,validation,expression,call,builtin,tanh:must_use:*": { "subcaseMS": 6.270 },
+ "webgpu:shader,validation,expression,call,builtin,tanh:values:*": { "subcaseMS": 4590.491 },
+ "webgpu:shader,validation,expression,call,builtin,textureGather:array_index_argument:*": { "subcaseMS": 1.123 },
+ "webgpu:shader,validation,expression,call,builtin,textureGather:component_argument,non_const:*": { "subcaseMS": 1.731 },
+ "webgpu:shader,validation,expression,call,builtin,textureGather:component_argument:*": { "subcaseMS": 1.321 },
+ "webgpu:shader,validation,expression,call,builtin,textureGather:coords_argument:*": { "subcaseMS": 1.352 },
+ "webgpu:shader,validation,expression,call,builtin,textureGather:offset_argument,non_const:*": { "subcaseMS": 1.231 },
+ "webgpu:shader,validation,expression,call,builtin,textureGather:offset_argument:*": { "subcaseMS": 1.534 },
+ "webgpu:shader,validation,expression,call,builtin,textureGatherCompare:array_index_argument:*": { "subcaseMS": 1.932 },
+ "webgpu:shader,validation,expression,call,builtin,textureGatherCompare:coords_argument:*": { "subcaseMS": 1.764 },
+ "webgpu:shader,validation,expression,call,builtin,textureGatherCompare:depth_ref_argument:*": { "subcaseMS": 1.753 },
+ "webgpu:shader,validation,expression,call,builtin,textureGatherCompare:offset_argument,non_const:*": { "subcaseMS": 1.534 },
+ "webgpu:shader,validation,expression,call,builtin,textureGatherCompare:offset_argument:*": { "subcaseMS": 1.243 },
+ "webgpu:shader,validation,expression,call,builtin,textureLoad:array_index_argument,non_storage:*": { "subcaseMS": 1.358 },
+ "webgpu:shader,validation,expression,call,builtin,textureLoad:array_index_argument,storage:*": { "subcaseMS": 1.906 },
+ "webgpu:shader,validation,expression,call,builtin,textureLoad:coords_argument,non_storage:*": { "subcaseMS": 1.717 },
+ "webgpu:shader,validation,expression,call,builtin,textureLoad:coords_argument,storage:*": { "subcaseMS": 1.750 },
+ "webgpu:shader,validation,expression,call,builtin,textureLoad:level_argument,non_storage:*": { "subcaseMS": 1.113 },
+ "webgpu:shader,validation,expression,call,builtin,textureLoad:sample_index_argument,non_storage:*": { "subcaseMS": 1.395 },
+ "webgpu:shader,validation,expression,call,builtin,textureSample:array_index_argument:*": { "subcaseMS": 1.888 },
+ "webgpu:shader,validation,expression,call,builtin,textureSample:coords_argument:*": { "subcaseMS": 1.342 },
+ "webgpu:shader,validation,expression,call,builtin,textureSample:offset_argument,non_const:*": { "subcaseMS": 1.604 },
+ "webgpu:shader,validation,expression,call,builtin,textureSample:offset_argument:*": { "subcaseMS": 1.401 },
+ "webgpu:shader,validation,expression,call,builtin,textureSample:only_in_fragment:*": { "subcaseMS": 1.121 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleBaseClampToEdge:coords_argument:*": { "subcaseMS": 239.356 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleBias:array_index_argument:*": { "subcaseMS": 1.630 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleBias:bias_argument:*": { "subcaseMS": 1.102 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleBias:coords_argument:*": { "subcaseMS": 1.938 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleBias:offset_argument,non_const:*": { "subcaseMS": 1.985 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleBias:offset_argument:*": { "subcaseMS": 1.081 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleBias:only_in_fragment:*": { "subcaseMS": 1.181 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompare:array_index_argument:*": { "subcaseMS": 1.932 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompare:coords_argument:*": { "subcaseMS": 1.282 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompare:depth_ref_argument:*": { "subcaseMS": 1.563 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompare:offset_argument,non_const:*": { "subcaseMS": 1.720 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompare:offset_argument:*": { "subcaseMS": 1.540 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompare:only_in_fragment:*": { "subcaseMS": 1.121 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompareLevel:array_index_argument:*": { "subcaseMS": 1.989 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompareLevel:coords_argument:*": { "subcaseMS": 1.932 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompareLevel:depth_ref_argument:*": { "subcaseMS": 1.578 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompareLevel:offset_argument,non_const:*": { "subcaseMS": 1.347 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleCompareLevel:offset_argument:*": { "subcaseMS": 1.619 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleGrad:array_index_argument:*": { "subcaseMS": 1.740 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleGrad:coords_argument:*": { "subcaseMS": 1.802 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleGrad:ddX_argument:*": { "subcaseMS": 1.882 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleGrad:ddY_argument:*": { "subcaseMS": 1.515 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleGrad:offset_argument,non_const:*": { "subcaseMS": 1.987 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleGrad:offset_argument:*": { "subcaseMS": 1.317 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleLevel:array_index_argument:*": { "subcaseMS": 1.888 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleLevel:coords_argument:*": { "subcaseMS": 1.342 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleLevel:level_argument:*": { "subcaseMS": 1.422 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleLevel:offset_argument,non_const:*": { "subcaseMS": 1.604 },
+ "webgpu:shader,validation,expression,call,builtin,textureSampleLevel:offset_argument:*": { "subcaseMS": 1.401 },
+ "webgpu:shader,validation,expression,call,builtin,textureStore:array_index_argument:*": { "subcaseMS": 1.240 },
+ "webgpu:shader,validation,expression,call,builtin,textureStore:coords_argument:*": { "subcaseMS": 1.350 },
+ "webgpu:shader,validation,expression,call,builtin,textureStore:value_argument:*": { "subcaseMS": 1.442 },
+ "webgpu:shader,validation,expression,call,builtin,trunc:args:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,trunc:must_use:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,trunc:values:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,unpack4xI8:args:*": { "subcaseMS": 23.923 },
+ "webgpu:shader,validation,expression,call,builtin,unpack4xI8:must_use:*": { "subcaseMS": 35.200 },
+ "webgpu:shader,validation,expression,call,builtin,unpack4xI8:supported:*": { "subcaseMS": 24.150 },
+ "webgpu:shader,validation,expression,call,builtin,unpack4xI8:unsupported:*": { "subcaseMS": 615.301 },
+ "webgpu:shader,validation,expression,call,builtin,unpack4xU8:args:*": { "subcaseMS": 21.830 },
+ "webgpu:shader,validation,expression,call,builtin,unpack4xU8:must_use:*": { "subcaseMS": 32.800 },
+ "webgpu:shader,validation,expression,call,builtin,unpack4xU8:supported:*": { "subcaseMS": 98.501 },
+ "webgpu:shader,validation,expression,call,builtin,unpack4xU8:unsupported:*": { "subcaseMS": 346.801 },
+ "webgpu:shader,validation,expression,call,builtin,workgroupUniformLoad:no_atomics:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,call,builtin,workgroupUniformLoad:only_in_compute:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,expression,overload_resolution:implicit_conversions:*": { "subcaseMS": 519.260 },
+ "webgpu:shader,validation,expression,overload_resolution:overload_resolution:*": { "subcaseMS": 436.603 },
+ "webgpu:shader,validation,expression,precedence:binary_requires_parentheses:*": { "subcaseMS": 149.921 },
+ "webgpu:shader,validation,expression,precedence:mixed_logical_requires_parentheses:*": { "subcaseMS": 6.107 },
+ "webgpu:shader,validation,expression,precedence:other:*": { "subcaseMS": 5.459 },
+ "webgpu:shader,validation,expression,unary,address_of_and_indirection:basic:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,expression,unary,address_of_and_indirection:composite:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,expression,unary,address_of_and_indirection:invalid:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,expression,unary,arithmetic_negation:invalid_types:*": { "subcaseMS": 25.894 },
+ "webgpu:shader,validation,expression,unary,arithmetic_negation:scalar_vector:*": { "subcaseMS": 31.344 },
+ "webgpu:shader,validation,expression,unary,bitwise_complement:invalid_types:*": { "subcaseMS": 7.564 },
+ "webgpu:shader,validation,expression,unary,bitwise_complement:scalar_vector:*": { "subcaseMS": 73.852 },
+ "webgpu:shader,validation,expression,unary,logical_negation:invalid_types:*": { "subcaseMS": 7.100 },
+ "webgpu:shader,validation,expression,unary,logical_negation:scalar_vector:*": { "subcaseMS": 84.680 },
+ "webgpu:shader,validation,extension,pointer_composite_access:deref:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,extension,pointer_composite_access:pointer:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,extension,readonly_and_readwrite_storage_textures:textureBarrier:*": { "subcaseMS": 1.141 },
+ "webgpu:shader,validation,extension,readonly_and_readwrite_storage_textures:var_decl:*": { "subcaseMS": 42.299 },
"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_atomic_pointer_one_module_scope:*": { "subcaseMS": 0.000 },
"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_atomic_pointers:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,functions,alias_analysis:two_atomic_pointers_to_array_elements:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,functions,alias_analysis:two_atomic_pointers_to_struct_members:*": { "subcaseMS": 0.000 },
"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,alias_analysis:two_pointers_to_array_elements:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,functions,alias_analysis:two_pointers_to_array_elements_indirect:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,functions,alias_analysis:two_pointers_to_struct_members:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,functions,alias_analysis:two_pointers_to_struct_members_indirect:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,functions,alias_analysis:workgroup_uniform_load:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,functions,restrictions:call_arg_types_match_1_param:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,functions,restrictions:call_arg_types_match_2_params:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,functions,restrictions:call_arg_types_match_3_params:*": { "subcaseMS": 0.000 },
"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 },
@@ -1774,17 +2271,23 @@
"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,break_if:non_bool_param:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,break_if:placement:*": { "subcaseMS": 0.000 },
"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,compound:parse:*": { "subcaseMS": 4.315 },
"webgpu:shader,validation,parse,const:placement:*": { "subcaseMS": 1.167 },
"webgpu:shader,validation,parse,const_assert:parse:*": { "subcaseMS": 1.400 },
+ "webgpu:shader,validation,parse,continuing:placement:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,diagnostic:after_other_directives:*": { "subcaseMS": 1.000 },
"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:diagnostic_scoping:*": { "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 },
@@ -1814,14 +2317,17 @@
"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,must_use:ignore_result_of_non_must_use_that_returns_call_of_must_use:*": { "subcaseMS": 0.000 },
"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:extra_on_compute_function:*": { "subcaseMS": 2.651 },
+ "webgpu:shader,validation,parse,pipeline_stage:extra_on_fragment_function:*": { "subcaseMS": 1.001 },
+ "webgpu:shader,validation,parse,pipeline_stage:extra_on_vertex_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,requires:requires:*": { "subcaseMS": 1.000 },
+ "webgpu:shader,validation,parse,requires:wgsl_matches_api:*": { "subcaseMS": 1.000 },
"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 },
@@ -1830,6 +2336,7 @@
"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_diagnostic:*": { "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 },
@@ -1848,6 +2355,7 @@
"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_requires:*": { "subcaseMS": 1.000 },
"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 },
@@ -1861,16 +2369,28 @@
"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,shadow_builtins:function_param:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,shadow_builtins:shadow_hides_access_mode:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,shadow_builtins:shadow_hides_builtin:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,shadow_builtins:shadow_hides_builtin_atomic:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,shadow_builtins:shadow_hides_builtin_atomic_type:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,shadow_builtins:shadow_hides_builtin_barriers:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,shadow_builtins:shadow_hides_builtin_f16:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,shadow_builtins:shadow_hides_builtin_handle_type:*": { "subcaseMS": 0.000 },
+ "webgpu:shader,validation,parse,shadow_builtins:shadow_hides_builtin_texture:*": { "subcaseMS": 0.000 },
"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,statement_behavior:invalid_functions:*": { "subcaseMS": 1.107 },
+ "webgpu:shader,validation,parse,statement_behavior:invalid_statements:*": { "subcaseMS": 80.699 },
+ "webgpu:shader,validation,parse,statement_behavior:valid_functions:*": { "subcaseMS": 0.978 },
+ "webgpu:shader,validation,parse,statement_behavior:valid_statements:*": { "subcaseMS": 15.781 },
"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 },
@@ -1884,7 +2404,6 @@
"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 },
@@ -1905,13 +2424,16 @@
"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,layout_constraints:layout_constraints:*": { "subcaseMS": 1.000 },
"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:out_of_order:*": { "subcaseMS": 1.296 },
"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_creation_fixed_footprint:*": { "subcaseMS": 1.000 },
"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 },
@@ -1921,6 +2443,8 @@
"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:any_type:*": { "subcaseMS": 42.329 },
+ "webgpu:shader,validation,types,alias:match_non_alias:*": { "subcaseMS": 3.767 },
"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 },
@@ -1931,12 +2455,23 @@
"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,array:invalid:*": { "subcaseMS": 2.470 },
+ "webgpu:shader,validation,types,array:valid:*": { "subcaseMS": 449.293 },
+ "webgpu:shader,validation,types,atomics:address_space:*": { "subcaseMS": 1.050 },
+ "webgpu:shader,validation,types,atomics:invalid_operations:*": { "subcaseMS": 1.050 },
+ "webgpu:shader,validation,types,atomics:type:*": { "subcaseMS": 1.050 },
+ "webgpu:shader,validation,types,matrix:invalid:*": { "subcaseMS": 68.086 },
+ "webgpu:shader,validation,types,matrix:valid:*": { "subcaseMS": 67.784 },
"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,textures:sampled_texture_types:*": { "subcaseMS": 7.634 },
+ "webgpu:shader,validation,types,textures:storage_texture_types:*": { "subcaseMS": 167.510 },
+ "webgpu:shader,validation,types,textures:texel_formats,as_value:*": { "subcaseMS": 0.518 },
+ "webgpu:shader,validation,types,textures:texel_formats:*": { "subcaseMS": 1707.432 },
"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 },
@@ -1948,6 +2483,7 @@
"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,color_space_conversions:util_matches_2d_canvas:*": { "subcaseMS": 1.001 },
"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 },
@@ -1994,9 +2530,11 @@
"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,sample:*": { "subcaseMS": 34.968 },
"webgpu:web_platform,external_texture,video:importExternalTexture,sampleWithVideoFrameWithVisibleRectParam:*": { "subcaseMS": 29.160 },
- "webgpu:web_platform,worker,worker:worker:*": { "subcaseMS": 245.901 },
+ "webgpu:web_platform,external_texture,video:importExternalTexture,sample_non_YUV_video_frame:*": { "subcaseMS": 36.270 },
+ "webgpu:web_platform,worker,worker:dedicated_worker:*": { "subcaseMS": 245.901 },
+ "webgpu:web_platform,worker,worker:service_worker:*": { "subcaseMS": 102.800 },
+ "webgpu:web_platform,worker,worker:shared_worker:*": { "subcaseMS": 26.801 },
"_end": ""
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/multisample_info.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/multisample_info.ts
new file mode 100644
index 0000000000..f8d247a3de
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/multisample_info.ts
@@ -0,0 +1,75 @@
+/* Data used for multisample tests */
+
+const samplePositionToFragmentPosition = (pos: readonly number[]): readonly number[] =>
+ pos.map(v => v / 16);
+const samplePositionsToFragmentPositions = (
+ positions: readonly (readonly number[])[]
+): readonly (readonly number[])[] => positions.map(samplePositionToFragmentPosition);
+
+// These are sample positions based on a 16x16 grid with 0,0 at the top left.
+// For example 8,8 would be a fragment coordinate of 0.5, 0.5
+// Based on: https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_standard_multisample_quality_levels
+const kMultisamplingTables = new Map<number, readonly (readonly number[])[]>([
+ [1, samplePositionsToFragmentPositions([[8, 8]])],
+ [
+ 2,
+ samplePositionsToFragmentPositions([
+ [4, 4],
+ [12, 12],
+ ]),
+ ],
+ [
+ 4,
+ samplePositionsToFragmentPositions([
+ [6, 2],
+ [14, 6],
+ [2, 10],
+ [10, 14],
+ ]),
+ ],
+ [
+ 8,
+ samplePositionsToFragmentPositions([
+ [9, 5],
+ [7, 11],
+ [13, 9],
+ [5, 3],
+ [3, 13],
+ [1, 7],
+ [11, 15],
+ [15, 1],
+ ]),
+ ],
+ [
+ 16,
+ samplePositionsToFragmentPositions([
+ [9, 9],
+ [7, 5],
+ [5, 10],
+ [12, 7],
+
+ [3, 6],
+ [10, 13],
+ [13, 11],
+ [11, 3],
+
+ [6, 14],
+ [8, 1],
+ [4, 2],
+ [2, 12],
+
+ [0, 8],
+ [15, 4],
+ [14, 15],
+ [1, 0],
+ ]),
+ ],
+]);
+
+/**
+ * For a given sampleCount returns an array of 2d fragment offsets
+ * where each offset is between 0 and 1.
+ */
+export function getMultisampleFragmentOffsets(sampleCount: number) {
+ return kMultisamplingTables.get(sampleCount)!;
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/print_environment.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/print_environment.spec.ts
new file mode 100644
index 0000000000..44a34c22cb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/print_environment.spec.ts
@@ -0,0 +1,70 @@
+export const description = `
+
+Examples of writing CTS tests with various features.
+
+Start here when looking for examples of basic framework usage.
+`;
+
+import { getResourcePath } from '../common/framework/resources.js';
+import { globalTestConfig } from '../common/framework/test_config.js';
+import { makeTestGroup } from '../common/framework/test_group.js';
+import { getDefaultRequestAdapterOptions } from '../common/util/navigator_gpu.js';
+
+import { GPUTest } from './gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+/** console.log is disallowed by WPT. Work around it when we're not in WPT. */
+function consoleLogIfNotWPT(x: unknown) {
+ if (!('step_timeout' in globalThis)) {
+ const cons = console;
+ cons.log(x);
+ }
+}
+
+g.test('info')
+ .desc(
+ `Test which prints what global scope (e.g. worker type) it's running in.
+Typically, tests will check for the presence of the feature they need (like HTMLCanvasElement)
+and skip if it's not available.
+
+Run this test under various configurations to see different results
+(Window, worker scopes, Node, etc.)
+
+NOTE: If your test runtime elides logs when tests pass, you won't see the prints from this test
+in the logs. On non-WPT runtimes, it will also print to the console with console.log.
+WPT disallows console.log and doesn't support logs on passing tests, so this does nothing on WPT.`
+ )
+ .fn(async t => {
+ const adapterInfo = await t.adapter.requestAdapterInfo();
+
+ const info = JSON.stringify(
+ {
+ globalScope: Object.getPrototypeOf(globalThis).constructor.name,
+ globalTestConfig,
+ baseResourcePath: getResourcePath(''),
+ defaultRequestAdapterOptions: getDefaultRequestAdapterOptions(),
+ adapterInfo,
+ userAgent: navigator.userAgent,
+ },
+ // Flatten objects with prototype chains into plain objects, using `for..in`. (Otherwise,
+ // properties from the prototype chain will be ignored and things will print as `{}`.)
+ (_key, value) => {
+ if (value === undefined || value === null) return null;
+ if (typeof value !== 'object') return value;
+
+ const valueObj = value as Record<string, unknown>;
+ return Object.fromEntries(
+ (function* () {
+ for (const key in valueObj) {
+ yield [key, valueObj[key]];
+ }
+ })()
+ );
+ },
+ 2
+ );
+
+ t.info(info);
+ consoleLogIfNotWPT(info);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/array/index.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/array/index.spec.ts
new file mode 100644
index 0000000000..c98cf318a2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/array/index.spec.ts
@@ -0,0 +1,354 @@
+export const description = `
+Execution Tests for array indexing expressions
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ False,
+ True,
+ Type,
+ Value,
+ array,
+ f32,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { align } from '../../../../../util/math.js';
+import { Case } from '../../case.js';
+import { allInputSources, basicExpressionBuilder, run } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('concrete_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#array-access-expr')
+ .desc(`Test indexing of an array of concrete scalars`)
+ .params(u =>
+ u
+ .combine(
+ 'inputSource',
+ // 'uniform' address space requires array stride to be multiple of 16 bytes
+ allInputSources.filter(s => s !== 'uniform')
+ )
+ .combine('elementType', ['i32', 'u32', 'f32', 'f16'] as const)
+ .combine('indexType', ['i32', 'u32'] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.elementType === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.elementType];
+ const indexType = Type[t.params.indexType];
+ const cases: Case[] = [
+ {
+ input: [
+ array(
+ /* 0 */ elementType.create(10),
+ /* 1 */ elementType.create(11),
+ /* 2 */ elementType.create(12)
+ ),
+ indexType.create(0),
+ ],
+ expected: elementType.create(10),
+ },
+ {
+ input: [
+ array(
+ /* 0 */ elementType.create(20),
+ /* 1 */ elementType.create(21),
+ /* 2 */ elementType.create(22)
+ ),
+ indexType.create(1),
+ ],
+ expected: elementType.create(21),
+ },
+ {
+ input: [
+ array(
+ /* 0 */ elementType.create(30),
+ /* 1 */ elementType.create(31),
+ /* 2 */ elementType.create(32)
+ ),
+ indexType.create(2),
+ ],
+ expected: elementType.create(32),
+ },
+ ];
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}]`),
+ [Type.array(3, elementType), indexType],
+ elementType,
+ t.params,
+ cases
+ );
+ });
+
+g.test('bool')
+ .specURL('https://www.w3.org/TR/WGSL/#array-access-expr')
+ .desc(`Test indexing of an array of booleans`)
+ .params(u =>
+ u
+ .combine(
+ 'inputSource',
+ // 'uniform' address space requires array stride to be multiple of 16 bytes
+ allInputSources.filter(s => s !== 'uniform')
+ )
+ .combine('indexType', ['i32', 'u32'] as const)
+ )
+ .fn(async t => {
+ const indexType = Type[t.params.indexType];
+ const cases: Case[] = [
+ {
+ input: [array(True, False, True), indexType.create(0)],
+ expected: True,
+ },
+ {
+ input: [array(True, False, True), indexType.create(1)],
+ expected: False,
+ },
+ {
+ input: [array(True, False, True), indexType.create(2)],
+ expected: True,
+ },
+ ];
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}]`),
+ [Type.array(3, Type.bool), indexType],
+ Type.bool,
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#array-access-expr')
+ .desc(`Test indexing of an array of scalars`)
+ .params(u =>
+ u
+ .combine('elementType', ['abstract-int', 'abstract-float'] as const)
+ .combine('indexType', ['i32', 'u32'] as const)
+ )
+ .fn(async t => {
+ const elementType = Type[t.params.elementType];
+ const indexType = Type[t.params.indexType];
+ const cases: Case[] = [
+ {
+ input: [
+ array(
+ /* 0 */ elementType.create(0x10_00000000),
+ /* 1 */ elementType.create(0x11_00000000),
+ /* 2 */ elementType.create(0x12_00000000)
+ ),
+ indexType.create(0),
+ ],
+ expected: f32(0x10),
+ },
+ {
+ input: [
+ array(
+ /* 0 */ elementType.create(0x20_00000000),
+ /* 1 */ elementType.create(0x21_00000000),
+ /* 2 */ elementType.create(0x22_00000000)
+ ),
+ indexType.create(1),
+ ],
+ expected: f32(0x21),
+ },
+ {
+ input: [
+ array(
+ /* 0 */ elementType.create(0x30_00000000),
+ /* 1 */ elementType.create(0x31_00000000),
+ /* 2 */ elementType.create(0x32_00000000)
+ ),
+ indexType.create(2),
+ ],
+ expected: f32(0x32),
+ },
+ ];
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}] / 0x100000000`),
+ [Type.array(3, elementType), indexType],
+ Type.f32,
+ { inputSource: 'const' },
+ cases
+ );
+ });
+
+g.test('runtime_sized')
+ .specURL('https://www.w3.org/TR/WGSL/#array-access-expr')
+ .desc(`Test indexing of a runtime sized array`)
+ .params(u =>
+ u
+ .combine('elementType', [
+ 'i32',
+ 'u32',
+ 'f32',
+ 'f16',
+ 'vec4i',
+ 'vec2u',
+ 'vec3f',
+ 'vec2h',
+ ] as const)
+ .combine('indexType', ['i32', 'u32'] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(Type[t.params.elementType]).kind === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const elementType = Type[t.params.elementType];
+ const valueArrayType = Type.array(0, elementType);
+ const indexType = Type[t.params.indexType];
+ const indexArrayType = Type.array(0, indexType);
+
+ const wgsl = `
+${scalarTypeOf(elementType).kind === 'f16' ? 'enable f16;' : ''}
+
+@group(0) @binding(0) var<storage, read> input_values : ${valueArrayType};
+@group(0) @binding(1) var<storage, read> input_indices : ${indexArrayType};
+@group(0) @binding(2) var<storage, read_write> output : ${valueArrayType};
+
+@compute @workgroup_size(16)
+fn main(@builtin(local_invocation_index) invocation_id : u32) {
+ let index = input_indices[invocation_id];
+ output[invocation_id] = input_values[index];
+}
+`;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const values = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53];
+ const indices = [9, 0, 14, 10, 12, 4, 15, 3, 5, 6, 11, 2, 8, 13, 7, 1];
+
+ const inputValues = values.map(i => elementType.create(i));
+ const inputIndices = indices.map(i => indexType.create(i));
+ const expected = indices.map(i => inputValues[i]);
+
+ const bufferSize = (arr: Value[]) => {
+ let offset = 0;
+ let alignment = 0;
+ for (const value of arr) {
+ alignment = Math.max(alignment, value.type.alignment);
+ offset = align(offset, value.type.alignment) + value.type.size;
+ }
+ return align(offset, alignment);
+ };
+
+ const toArray = (arr: Value[]) => {
+ const array = new Uint8Array(bufferSize(arr));
+ let offset = 0;
+ for (const value of arr) {
+ offset = align(offset, value.type.alignment);
+ value.copyTo(array, offset);
+ offset += value.type.size;
+ }
+ return array;
+ };
+
+ const inputArrayBuffer = t.makeBufferWithContents(toArray(inputValues), GPUBufferUsage.STORAGE);
+ const inputIndexBuffer = t.makeBufferWithContents(
+ toArray(inputIndices),
+ GPUBufferUsage.STORAGE
+ );
+ const outputBuffer = t.device.createBuffer({
+ size: bufferSize(expected),
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: inputArrayBuffer } },
+ { binding: 1, resource: { buffer: inputIndexBuffer } },
+ { binding: 2, resource: { buffer: outputBuffer } },
+ ],
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(outputBuffer, toArray(expected));
+ });
+
+g.test('vector')
+ .specURL('https://www.w3.org/TR/WGSL/#array-access-expr')
+ .desc(`Test indexing of an array of vectors`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .expand('elementType', t =>
+ t.inputSource === 'uniform'
+ ? (['vec4i', 'vec4u', 'vec4f'] as const)
+ : (['vec4i', 'vec4u', 'vec4f', 'vec4h'] as const)
+ )
+ .combine('indexType', ['i32', 'u32'] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.elementType === 'vec4h') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.elementType];
+ const indexType = Type[t.params.indexType];
+ const cases: Case[] = [
+ {
+ input: [
+ array(
+ /* 0 */ elementType.create([0x10, 0x11, 0x12, 0x13]),
+ /* 1 */ elementType.create([0x14, 0x15, 0x16, 0x17]),
+ /* 2 */ elementType.create([0x18, 0x19, 0x1a, 0x1b])
+ ),
+ indexType.create(0),
+ ],
+ expected: elementType.create([0x10, 0x11, 0x12, 0x13]),
+ },
+ {
+ input: [
+ array(
+ /* 0 */ elementType.create([0x20, 0x21, 0x22, 0x23]),
+ /* 1 */ elementType.create([0x24, 0x25, 0x26, 0x27]),
+ /* 2 */ elementType.create([0x28, 0x29, 0x2a, 0x2b])
+ ),
+ indexType.create(1),
+ ],
+ expected: elementType.create([0x24, 0x25, 0x26, 0x27]),
+ },
+ {
+ input: [
+ array(
+ /* 0 */ elementType.create([0x30, 0x31, 0x32, 0x33]),
+ /* 1 */ elementType.create([0x34, 0x35, 0x36, 0x37]),
+ /* 2 */ elementType.create([0x38, 0x39, 0x3a, 0x3b])
+ ),
+ indexType.create(2),
+ ],
+ expected: elementType.create([0x38, 0x39, 0x3a, 0x3b]),
+ },
+ ];
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}]`),
+ [Type.array(3, elementType), indexType],
+ elementType,
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/matrix/index.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/matrix/index.spec.ts
new file mode 100644
index 0000000000..f6fd05b46f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/matrix/index.spec.ts
@@ -0,0 +1,200 @@
+export const description = `
+Execution Tests for matrix indexing expressions
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import {
+ MatrixValue,
+ ScalarValue,
+ Type,
+ abstractFloat,
+ f32,
+ vec,
+} from '../../../../../util/conversion.js';
+import { Case } from '../../case.js';
+import { allInputSources, basicExpressionBuilder, run } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('concrete_float_column')
+ .specURL('https://www.w3.org/TR/WGSL/#matrix-access-expr')
+ .desc(`Test indexing a column vector from a concrete matrix`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('elementType', ['f32', 'f16'] as const)
+ .combine('indexType', ['i32', 'u32'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.elementType === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.elementType];
+ const indexType = Type[t.params.indexType];
+ const matrixType = Type.mat(t.params.columns, t.params.rows, elementType);
+ const columnType = Type.vec(t.params.rows, elementType);
+ const elements: ScalarValue[][] = [];
+ for (let c = 0; c < t.params.columns; c++) {
+ const column: ScalarValue[] = [];
+ for (let r = 0; r < t.params.rows; r++) {
+ column.push(elementType.create((c + 1) * 10 + (r + 1)));
+ }
+ elements.push(column);
+ }
+ const vector = new MatrixValue(elements);
+ const cases: Case[] = [];
+ for (let c = 0; c < t.params.columns; c++) {
+ cases.push({
+ input: [vector, indexType.create(c)],
+ expected: vec(...elements[c]),
+ });
+ }
+
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}]`),
+ [matrixType, indexType],
+ columnType,
+ t.params,
+ cases
+ );
+ });
+
+g.test('concrete_float_element')
+ .specURL('https://www.w3.org/TR/WGSL/#matrix-access-expr')
+ .desc(`Test indexing a single element from a concrete matrix`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('elementType', ['f32', 'f16'] as const)
+ .combine('indexType', ['i32', 'u32'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.elementType === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.elementType];
+ const indexType = Type[t.params.indexType];
+ const matrixType = Type.mat(t.params.columns, t.params.rows, elementType);
+ const columnValues: ScalarValue[][] = [];
+ for (let c = 0; c < t.params.columns; c++) {
+ const column: ScalarValue[] = [];
+ for (let r = 0; r < t.params.rows; r++) {
+ column.push(elementType.create((c + 1) * 10 + (r + 1)));
+ }
+ columnValues.push(column);
+ }
+ const matrix = new MatrixValue(columnValues);
+ const cases: Case[] = [];
+ for (let c = 0; c < t.params.columns; c++) {
+ for (let r = 0; r < t.params.rows; r++) {
+ cases.push({
+ input: [matrix, indexType.create(c), indexType.create(r)],
+ expected: columnValues[c][r],
+ });
+ }
+ }
+
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}][${ops[2]}]`),
+ [matrixType, indexType, indexType],
+ elementType,
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_float_column')
+ .specURL('https://www.w3.org/TR/WGSL/#matrix-access-expr')
+ .desc(`Test indexing a column vector from a abstract-float matrix`)
+ .params(u =>
+ u
+ .combine('indexType', ['i32', 'u32'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const indexType = Type[t.params.indexType];
+ const matrixType = Type.mat(t.params.columns, t.params.rows, Type.abstractFloat);
+ const vecfColumnType = Type.vec(t.params.rows, Type.f32);
+ const values: number[][] = [];
+ for (let c = 0; c < t.params.columns; c++) {
+ const column: number[] = [];
+ for (let r = 0; r < t.params.rows; r++) {
+ column.push((c + 1) * 10 + (r + 1));
+ }
+ values.push(column);
+ }
+ const matrix = new MatrixValue(
+ values.map(column => column.map(v => abstractFloat(v * 0x100000000)))
+ );
+ const cases: Case[] = [];
+ for (let c = 0; c < t.params.columns; c++) {
+ cases.push({
+ input: [matrix, indexType.create(c)],
+ expected: vec(...values[c].map(v => f32(v))),
+ });
+ }
+
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}] / 0x100000000`),
+ [matrixType, indexType],
+ vecfColumnType,
+ { inputSource: 'const' },
+ cases
+ );
+ });
+
+g.test('abstract_float_element')
+ .specURL('https://www.w3.org/TR/WGSL/#matrix-access-expr')
+ .desc(`Test indexing a single element from a abstract-float matrix`)
+ .params(u =>
+ u
+ .combine('indexType', ['i32', 'u32'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const indexType = Type[t.params.indexType];
+ const matrixType = Type.mat(t.params.columns, t.params.rows, Type.abstractFloat);
+ const values: number[][] = [];
+ for (let c = 0; c < t.params.columns; c++) {
+ const column: number[] = [];
+ for (let r = 0; r < t.params.rows; r++) {
+ column.push((c + 1) * 10 + (r + 1));
+ }
+ values.push(column);
+ }
+ const matrix = new MatrixValue(
+ values.map(column => column.map(v => abstractFloat(v * 0x100000000)))
+ );
+ const cases: Case[] = [];
+ for (let c = 0; c < t.params.columns; c++) {
+ for (let r = 0; r < t.params.rows; r++) {
+ cases.push({
+ input: [matrix, indexType.create(c), indexType.create(r)],
+ expected: f32(values[c][r]),
+ });
+ }
+ }
+
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}][${ops[2]}] / 0x100000000`),
+ [matrixType, indexType, indexType],
+ Type.f32,
+ { inputSource: 'const' },
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/structure/index.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/structure/index.spec.ts
new file mode 100644
index 0000000000..ae56d11b6b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/structure/index.spec.ts
@@ -0,0 +1,508 @@
+export const description = `
+Execution Tests for structure member accessing expressions
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { ScalarKind, Type, Value, u32 } from '../../../../../util/conversion.js';
+import { align } from '../../../../../util/math.js';
+import { toComparator } from '../../expectation.js';
+import { InputSource, structLayout, structStride } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const kMemberTypes = [
+ ['bool'],
+ ['u32'],
+ ['vec3f'],
+ ['i32', 'u32'],
+ ['i32', 'f16', 'vec4i', 'mat3x2f'],
+ ['bool', 'u32', 'f16', 'vec3f', 'vec2i'],
+ ['i32', 'u32', 'f32', 'f16', 'vec3f', 'vec4i'],
+] as readonly ScalarKind[][];
+
+const kMemberTypesNoBool = kMemberTypes.filter(tys => !tys.includes('bool'));
+
+async function run(
+ t: GPUTest,
+ wgsl: string,
+ expected: Value[],
+ input: Value[] | ArrayBufferLike | null,
+ inputSource: InputSource
+) {
+ const outputBufferSize = structStride(
+ expected.map(v => v.type),
+ 'storage_rw'
+ );
+
+ const outputBuffer = t.device.createBuffer({
+ size: outputBufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
+ });
+
+ const bindGroupEntries: GPUBindGroupEntry[] = [
+ {
+ binding: 0,
+ resource: { buffer: outputBuffer },
+ },
+ ];
+
+ if (input !== null) {
+ let inputData: Uint8Array;
+ if (input instanceof Array) {
+ const inputTypes = input.map(v => v.type);
+ const inputBufferSize = structStride(inputTypes, inputSource);
+ inputData = new Uint8Array(inputBufferSize);
+ structLayout(inputTypes, inputSource, m => {
+ input[m.index].copyTo(inputData, m.offset);
+ });
+ } else {
+ inputData = new Uint8Array(input);
+ }
+
+ const inputBuffer = t.makeBufferWithContents(
+ inputData,
+ GPUBufferUsage.COPY_SRC |
+ (inputSource === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE)
+ );
+
+ bindGroupEntries.push({
+ binding: 1,
+ resource: { buffer: inputBuffer },
+ });
+ }
+
+ // build the shader module
+ const module = t.device.createShaderModule({ code: wgsl });
+
+ // build the pipeline
+ const pipeline = await t.device.createComputePipelineAsync({
+ layout: 'auto',
+ compute: { module, entryPoint: 'main' },
+ });
+
+ // build the bind group
+ const group = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: bindGroupEntries,
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, group);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+
+ t.queue.submit([encoder.finish()]);
+
+ const checkExpectation = (outputData: Uint8Array) => {
+ // The list of expectation failures
+ const errs: string[] = [];
+
+ let offset = 0;
+ for (let i = 0; i < expected.length; i++) {
+ offset = align(offset, expected[i].type.alignment);
+ const got = expected[i].type.read(outputData, offset);
+ const cmp = toComparator(expected[i]).compare(got);
+ if (!cmp.matched) {
+ errs.push(`result ${i}:)
+ returned: ${cmp.got}
+ expected: ${cmp.expected}`);
+ }
+ offset += expected[i].type.size;
+ }
+
+ return errs.length > 0 ? new Error(errs.join('\n\n')) : undefined;
+ };
+
+ t.expectGPUBufferValuesPassCheck(outputBuffer, checkExpectation, {
+ type: Uint8Array,
+ typedLength: outputBufferSize,
+ });
+}
+
+g.test('buffer')
+ .specURL('https://www.w3.org/TR/WGSL/#struct-access-expr')
+ .desc(`Test accessing of a value structure in a storage or uniform buffer`)
+ .params(u =>
+ u
+ .combine('member_types', kMemberTypesNoBool)
+ .combine('inputSource', ['uniform', 'storage'] as const)
+ .beginSubcases()
+ .expand('member_index', t => t.member_types.map((_, i) => i))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.member_types.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const values = t.params.member_types.map((ty, i) => Type[ty].create(i));
+ const expected = values[t.params.member_index];
+
+ await run(
+ t,
+ `
+${t.params.member_types.includes('f16') ? 'enable f16;' : ''}
+
+@group(0) @binding(0) var<storage, read_write> output : ${expected.type};
+@group(0) @binding(1) var<${t.params.inputSource}> input : MyStruct;
+
+struct MyStruct {
+ ${t.params.member_types.map((ty, i) => ` member_${i} : ${ty},`).join('\n')}
+};
+
+@workgroup_size(1) @compute
+fn main() {
+ output = input.member_${t.params.member_index};
+}
+`,
+ /* expected */ [expected],
+ /* input */ values,
+ /* inputSource */ t.params.inputSource === 'uniform' ? 'uniform' : 'storage_r'
+ );
+ });
+
+g.test('buffer_align')
+ .specURL('https://www.w3.org/TR/WGSL/#struct-access-expr')
+ .desc(
+ `Test accessing of a value structure in a storage buffer that has members using the @align attribute`
+ )
+ .params(u =>
+ u
+ .beginSubcases()
+ .combine('member_index', [0, 1, 2] as const)
+ .combine('alignments', [
+ [1, 1, 1],
+ [4, 4, 4],
+ [4, 8, 16],
+ [8, 4, 16],
+ [8, 16, 4],
+ ] as const)
+ )
+ .fn(async t => {
+ const memberTypes = ['i32', 'u32', 'f32'] as const;
+ const values = memberTypes.map((ty, i) => Type[ty].create(i));
+ const expected = values[t.params.member_index];
+ const input = new Uint8Array(64);
+ let offset = 4; // pre : i32
+ for (let i = 0; i < 3; i++) {
+ offset = align(offset, t.params.alignments[i]);
+ values[i].copyTo(input, offset);
+ offset += values[i].type.size;
+ }
+ await run(
+ t,
+ `
+@group(0) @binding(0) var<storage, read_write> output : ${expected.type};
+@group(0) @binding(1) var<storage> input : MyStruct;
+
+struct MyStruct {
+ pre : i32,
+ @align(${t.params.alignments[0]}) member_0 : ${memberTypes[0]},
+ @align(${t.params.alignments[1]}) member_1 : ${memberTypes[1]},
+ @align(${t.params.alignments[2]}) member_2 : ${memberTypes[2]},
+ post : i32,
+};
+
+@workgroup_size(1) @compute
+fn main() {
+output = input.member_${t.params.member_index};
+}
+`,
+ /* expected */ [expected],
+ /* input */ input,
+ /* inputSource */ 'storage_r'
+ );
+ });
+
+g.test('buffer_size')
+ .specURL('https://www.w3.org/TR/WGSL/#struct-access-expr')
+ .desc(
+ `Test accessing of a value structure in a storage buffer that has members using the @size attribute`
+ )
+ .params(u =>
+ u
+ .beginSubcases()
+ .combine('member_index', [0, 1, 2] as const)
+ .combine('sizes', [
+ [4, 4, 4],
+ [4, 8, 16],
+ [8, 4, 16],
+ [8, 16, 4],
+ ] as const)
+ )
+ .fn(async t => {
+ const memberTypes = ['i32', 'u32', 'f32'] as const;
+ const values = memberTypes.map((ty, i) => Type[ty].create(i));
+ const expected = values[t.params.member_index];
+ const input = new Uint8Array(64);
+ let offset = 4; // pre : i32
+ for (let i = 0; i < 3; i++) {
+ offset = align(offset, values[i].type.alignment);
+ values[i].copyTo(input, offset);
+ offset += t.params.sizes[i];
+ }
+ await run(
+ t,
+ `
+@group(0) @binding(0) var<storage, read_write> output : ${expected.type};
+@group(0) @binding(1) var<storage> input : MyStruct;
+
+struct MyStruct {
+ pre : i32,
+ @size(${t.params.sizes[0]}) member_0 : ${memberTypes[0]},
+ @size(${t.params.sizes[1]}) member_1 : ${memberTypes[1]},
+ @size(${t.params.sizes[2]}) member_2 : ${memberTypes[2]},
+ post : i32,
+};
+
+@workgroup_size(1) @compute
+fn main() {
+output = input.member_${t.params.member_index};
+}
+`,
+ /* expected */ [expected],
+ /* input */ input,
+ /* inputSource */ 'storage_r'
+ );
+ });
+
+g.test('buffer_pointer')
+ .specURL('https://www.w3.org/TR/WGSL/#struct-access-expr')
+ .desc(`Test accessing of a value structure via a pointer to a storage or uniform buffer`)
+ .params(u =>
+ u
+ .combine('member_types', kMemberTypesNoBool)
+ .combine('inputSource', ['uniform', 'storage'] as const)
+ .beginSubcases()
+ .expand('member_index', t => t.member_types.map((_, i) => i))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.member_types.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const values = t.params.member_types.map((ty, i) => Type[ty].create(i));
+ const expected = values[t.params.member_index];
+
+ await run(
+ t,
+ `
+${t.params.member_types.includes('f16') ? 'enable f16;' : ''}
+
+@group(0) @binding(0) var<storage, read_write> output : ${expected.type};
+@group(0) @binding(1) var<${t.params.inputSource}> input : MyStruct;
+
+struct MyStruct {
+ ${t.params.member_types.map((ty, i) => ` member_${i} : ${ty},`).join('\n')}
+};
+
+@workgroup_size(1) @compute
+fn main() {
+ let ptr = &input;
+ output = (*ptr).member_${t.params.member_index};
+}
+`,
+ /* expected */ [expected],
+ /* input */ values,
+ /* inputSource */ t.params.inputSource === 'uniform' ? 'uniform' : 'storage_r'
+ );
+ });
+
+g.test('let')
+ .specURL('https://www.w3.org/TR/WGSL/#struct-access-expr')
+ .desc(`Test accessing of a let structure`)
+ .params(u =>
+ u
+ .combine('member_types', kMemberTypes)
+ .beginSubcases()
+ .expand('member_index', t => t.member_types.map((_, i) => i))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.member_types.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const memberType = Type[t.params.member_types[t.params.member_index]];
+ const values = t.params.member_types.map((ty, i) => Type[ty].create(i));
+ const expected =
+ memberType === Type.bool
+ ? u32(values[t.params.member_index].value === true ? 1 : 0)
+ : values[t.params.member_index];
+
+ await run(
+ t,
+ `
+${t.params.member_types.includes('f16') ? 'enable f16;' : ''}
+
+@group(0) @binding(0) var<storage, read_write> output : ${expected.type};
+
+struct MyStruct {
+ ${t.params.member_types.map((ty, i) => ` member_${i} : ${ty},`).join('\n')}
+};
+
+@workgroup_size(1) @compute
+fn main() {
+ let s = MyStruct(${values.map(v => v.wgsl()).join(', ')});
+ let v = s.member_${t.params.member_index};
+ output = ${memberType === Type.bool ? `select(0u, 1u, v)` : 'v'};
+}
+`,
+ /* expected */ [expected],
+ /* input */ null,
+ /* inputSource */ 'const'
+ );
+ });
+
+g.test('param')
+ .specURL('https://www.w3.org/TR/WGSL/#struct-access-expr')
+ .desc(`Test accessing of a parameter structure`)
+ .params(u =>
+ u
+ .combine('member_types', kMemberTypes)
+ .beginSubcases()
+ .expand('member_index', t => t.member_types.map((_, i) => i))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.member_types.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const memberType = Type[t.params.member_types[t.params.member_index]];
+ const values = t.params.member_types.map((ty, i) => Type[ty].create(i));
+ const expected =
+ memberType === Type.bool
+ ? u32(values[t.params.member_index].value === true ? 1 : 0)
+ : values[t.params.member_index];
+
+ await run(
+ t,
+ `
+${t.params.member_types.includes('f16') ? 'enable f16;' : ''}
+
+@group(0) @binding(0) var<storage, read_write> output : ${expected.type};
+
+struct MyStruct {
+ ${t.params.member_types.map((ty, i) => ` member_${i} : ${ty},`).join('\n')}
+};
+
+fn f(s : MyStruct) -> ${t.params.member_types[t.params.member_index]} {
+ return s.member_${t.params.member_index};
+}
+
+@workgroup_size(1) @compute
+fn main() {
+ let v = f(MyStruct(${values.map(v => v.wgsl()).join(', ')}));
+ output = ${memberType === Type.bool ? `select(0u, 1u, v)` : 'v'};
+}
+`,
+ /* expected */ [expected],
+ /* input */ null,
+ /* inputSource */ 'const'
+ );
+ });
+
+g.test('const')
+ .specURL('https://www.w3.org/TR/WGSL/#struct-access-expr')
+ .desc(`Test accessing of a const value structure`)
+ .params(u =>
+ u
+ .combine('member_types', kMemberTypes)
+ .beginSubcases()
+ .expand('member_index', t => t.member_types.map((_, i) => i))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.member_types.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const memberType = Type[t.params.member_types[t.params.member_index]];
+ const values = t.params.member_types.map((ty, i) => Type[ty].create(i));
+ const expected =
+ memberType === Type.bool
+ ? u32(values[t.params.member_index].value === true ? 1 : 0)
+ : values[t.params.member_index];
+
+ await run(
+ t,
+ `
+${t.params.member_types.includes('f16') ? 'enable f16;' : ''}
+
+@group(0) @binding(0) var<storage, read_write> output : ${expected.type};
+
+struct MyStruct {
+ ${t.params.member_types.map((ty, i) => ` member_${i} : ${ty},`).join('\n')}
+};
+
+const S = MyStruct(${values.map(v => v.wgsl()).join(', ')});
+
+@workgroup_size(1) @compute
+fn main() {
+ let v = S.member_${t.params.member_index};
+ output = ${memberType === Type.bool ? `select(0u, 1u, v)` : 'v'};
+}
+`,
+ /* expected */ [expected],
+ /* input */ null,
+ /* inputSource */ 'const'
+ );
+ });
+
+g.test('const_nested')
+ .specURL('https://www.w3.org/TR/WGSL/#struct-access-expr')
+ .desc(`Test accessing of a const value structure nested in another structure`)
+ .params(u =>
+ u
+ .combine('member_types', kMemberTypes)
+ .beginSubcases()
+ .expand('member_index', t => t.member_types.map((_, i) => i))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.member_types.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const memberType = Type[t.params.member_types[t.params.member_index]];
+ const values = t.params.member_types.map((ty, i) => Type[ty].create(i));
+ const expected =
+ memberType === Type.bool
+ ? u32(values[t.params.member_index].value === true ? 1 : 0)
+ : values[t.params.member_index];
+
+ await run(
+ t,
+ `
+${t.params.member_types.includes('f16') ? 'enable f16;' : ''}
+
+@group(0) @binding(0) var<storage, read_write> output : ${expected.type};
+
+struct MyStruct {
+ ${t.params.member_types.map((ty, i) => ` member_${i} : ${ty},`).join('\n')}
+};
+
+struct Outer {
+ pre : i32,
+ inner : MyStruct,
+ post : i32,
+}
+
+const S = Outer(10, MyStruct(${values.map(v => v.wgsl()).join(', ')}), 20);
+
+@workgroup_size(1) @compute
+fn main() {
+ let v = S.inner.member_${t.params.member_index};
+ output = ${memberType === Type.bool ? `select(0u, 1u, v)` : 'v'};
+}
+`,
+ /* expected */ [expected],
+ /* input */ null,
+ /* inputSource */ 'const'
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/vector/components.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/vector/components.spec.ts
new file mode 100644
index 0000000000..0528337087
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/vector/components.spec.ts
@@ -0,0 +1,118 @@
+export const description = `
+Execution Tests for vector component selection expressions
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { ScalarValue, Type, VectorValue, f32 } from '../../../../../util/conversion.js';
+import { allInputSources, basicExpressionBuilder, run } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+/** @returns the full permutation of component indices used to component select a vector of width 'n' */
+function indices(n: number) {
+ const out: number[][] = [];
+ for (let width = 1; width < n; width++) {
+ let generate = (swizzle: number[]) => {
+ out.push(swizzle);
+ };
+ for (let i = 0; i < width; i++) {
+ const next = generate;
+ generate = (swizzle: number[]) => {
+ for (let j = 0; j < width; j++) {
+ next([...swizzle, j]);
+ }
+ };
+ }
+ generate([]);
+ }
+ return out;
+}
+
+g.test('concrete_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-access-expr')
+ .desc(`Test vector component selection of concrete vectors`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('elementType', ['i32', 'u32', 'f32', 'f16', 'bool'] as const)
+ .combine('width', [2, 3, 4] as const)
+ .combine('components', ['rgba', 'xyzw'] as const)
+ .beginSubcases()
+ .expand('indices', u => indices(u.width))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.elementType === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.elementType];
+ const vectorType = Type.vec(t.params.width, elementType);
+ const elementValues =
+ t.params.elementType === 'bool' ? (i: number) => i & 1 : (i: number) => (i + 1) * 10;
+ const elements: ScalarValue[] = [];
+ for (let i = 0; i < t.params.width; i++) {
+ elements.push(elementType.create(elementValues(i)));
+ }
+ const result = (() => {
+ if (t.params.indices.length === 1) {
+ return { type: elementType, value: elementType.create(elementValues(0)) };
+ } else {
+ const vec = Type.vec(t.params.indices.length, elementType);
+ return { type: vec, value: vec.create(t.params.indices.map(i => elementValues(i))) };
+ }
+ })();
+
+ const components = t.params.indices.map(i => t.params.components[i]).join('');
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}.${components}`),
+ [vectorType],
+ result.type,
+ t.params,
+ [{ input: [new VectorValue(elements)], expected: result.value }]
+ );
+ });
+
+g.test('abstract_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-access-expr')
+ .desc(`Test vector component selection of abstract numeric vectors`)
+ .params(u =>
+ u
+ .combine('elementType', ['abstract-int', 'abstract-float'] as const)
+ .combine('width', [2, 3, 4] as const)
+ .combine('components', ['rgba', 'xyzw'] as const)
+ .beginSubcases()
+ .expand('indices', u => indices(u.width))
+ )
+ .fn(async t => {
+ const elementType = Type[t.params.elementType];
+ const vectorType = Type.vec(t.params.width, elementType);
+ const elementValues = (i: number) => (i + 1) * 0x100000000;
+ const elements: ScalarValue[] = [];
+ for (let i = 0; i < t.params.width; i++) {
+ elements.push(elementType.create(elementValues(i)));
+ }
+ const result = (() => {
+ if (t.params.indices.length === 1) {
+ return { type: Type.f32, value: f32(elementValues(0) / 0x100000000) };
+ } else {
+ const vec = Type.vec(t.params.indices.length, Type.f32);
+ return {
+ type: vec,
+ value: vec.create(t.params.indices.map(i => elementValues(i) / 0x100000000)),
+ };
+ }
+ })();
+
+ const components = t.params.indices.map(i => t.params.components[i]).join('');
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}.${components} / 0x100000000`),
+ [vectorType],
+ result.type,
+ { inputSource: 'const' },
+ [{ input: [new VectorValue(elements)], expected: result.value }]
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/vector/index.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/vector/index.spec.ts
new file mode 100644
index 0000000000..28fbd0e86c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/access/vector/index.spec.ts
@@ -0,0 +1,87 @@
+export const description = `
+Execution Tests for vector indexing expressions
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { ScalarValue, Type, VectorValue, f32 } from '../../../../../util/conversion.js';
+import { Case } from '../../case.js';
+import { allInputSources, basicExpressionBuilder, run } from '../../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('concrete_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-access-expr')
+ .desc(`Test indexing of concrete vectors`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('elementType', ['i32', 'u32', 'f32', 'f16', 'bool'] as const)
+ .combine('indexType', ['i32', 'u32'] as const)
+ .combine('width', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.elementType === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.elementType];
+ const indexType = Type[t.params.indexType];
+ const vectorType = Type.vec(t.params.width, elementType);
+ const elements: ScalarValue[] = [];
+ for (let i = 0; i < t.params.width; i++) {
+ if (t.params.elementType === 'bool') {
+ elements.push(elementType.create(i & 1));
+ } else {
+ elements.push(elementType.create((i + 1) * 10));
+ }
+ }
+ const vector = new VectorValue(elements);
+ const cases: Case[] = [];
+ for (let i = 0; i < t.params.width; i++) {
+ cases.push({ input: [vector, indexType.create(i)], expected: elements[i] });
+ }
+
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}]`),
+ [vectorType, indexType],
+ elementType,
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-access-expr')
+ .desc(`Test indexing of abstract numeric vectors`)
+ .params(u =>
+ u
+ .combine('elementType', ['abstract-int', 'abstract-float'] as const)
+ .combine('indexType', ['i32', 'u32'] as const)
+ .combine('width', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const elementType = Type[t.params.elementType];
+ const indexType = Type[t.params.indexType];
+ const vectorType = Type.vec(t.params.width, elementType);
+ const elements: ScalarValue[] = [];
+ for (let i = 0; i < t.params.width; i++) {
+ elements.push(elementType.create((i + 1) * 0x100000000));
+ }
+ const vector = new VectorValue(elements);
+ const cases: Case[] = [];
+ for (let i = 0; i < t.params.width; i++) {
+ cases.push({ input: [vector, indexType.create(i)], expected: f32(i + 1) });
+ }
+
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${ops[0]}[${ops[1]}] / 0x100000000`),
+ [vectorType, indexType],
+ Type.f32,
+ { inputSource: 'const' },
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_addition.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_addition.cache.ts
new file mode 100644
index 0000000000..f5e9d3b481
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_addition.cache.ts
@@ -0,0 +1,54 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF64Range, sparseVectorF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const additionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.abstract.toVector(v.map(e => FP.abstract.additionInterval(e, s)));
+};
+
+const additionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.abstract.toVector(v.map(e => FP.abstract.additionInterval(s, e)));
+};
+
+const scalar_cases = {
+ ['scalar']: () => {
+ return FP.abstract.generateScalarPairToIntervalCases(
+ sparseScalarF64Range(),
+ sparseScalarF64Range(),
+ 'finite',
+ FP.abstract.additionInterval
+ );
+ },
+};
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`vec${dim}_scalar`]: () => {
+ return FP.abstract.generateVectorScalarToVectorCases(
+ sparseVectorF64Range(dim),
+ sparseScalarF64Range(),
+ 'finite',
+ additionVectorScalarInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`scalar_vec${dim}`]: () => {
+ return FP.abstract.generateScalarVectorToVectorCases(
+ sparseScalarF64Range(),
+ sparseVectorF64Range(dim),
+ 'finite',
+ additionScalarVectorInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_addition', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts
index 0f703f0889..52a07ff328 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_addition.spec.ts
@@ -1,70 +1,17 @@
export const description = `
-Execution Tests for non-matrix AbstractFloat addition expression
+Execution Tests for non-matrix abstract-float 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 { Type } from '../../../../util/conversion.js';
import { onlyConstInputSource, run } from '../expression.js';
-import { abstractBinary } from './binary.js';
-
-const additionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.additionInterval(e, s)));
-};
-
-const additionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.additionInterval(s, e)));
-};
+import { d } from './af_addition.cache.js';
+import { abstractFloatBinary } from './binary.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = {
- ['scalar']: () => {
- return FP.abstract.generateScalarPairToIntervalCases(
- sparseF64Range(),
- sparseF64Range(),
- 'finite',
- FP.abstract.additionInterval
- );
- },
-};
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`vec${dim}_scalar`]: () => {
- return FP.abstract.generateVectorScalarToVectorCases(
- sparseVectorF64Range(dim),
- sparseF64Range(),
- 'finite',
- additionVectorScalarInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`scalar_vec${dim}`]: () => {
- return FP.abstract.generateScalarVectorToVectorCases(
- sparseF64Range(),
- sparseVectorF64Range(dim),
- 'finite',
- additionScalarVectorInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/af_addition', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -78,9 +25,9 @@ Accuracy: Correctly rounded
const cases = await d.get('scalar');
await run(
t,
- abstractBinary('+'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('+'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -101,9 +48,9 @@ Accuracy: Correctly rounded
const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases
await run(
t,
- abstractBinary('+'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('+'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -123,9 +70,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`vec${dim}_scalar`);
await run(
t,
- abstractBinary('+'),
- [TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('+'),
+ [Type.vec(dim, Type.abstractFloat), Type.abstractFloat],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
@@ -145,9 +92,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`scalar_vec${dim}`);
await run(
t,
- abstractBinary('+'),
- [TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('+'),
+ [Type.abstractFloat, Type.vec(dim, Type.abstractFloat)],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_comparison.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_comparison.cache.ts
new file mode 100644
index 0000000000..e000266401
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_comparison.cache.ts
@@ -0,0 +1,90 @@
+import { anyOf } from '../../../../util/compare.js';
+import { abstractFloat, bool, ScalarValue } from '../../../../util/conversion.js';
+import { flushSubnormalNumberF64, vectorF64Range } from '../../../../util/math.js';
+import { Case } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+/**
+ * @returns a test case for the provided left hand & right hand values and truth function.
+ * Handles quantization and subnormals.
+ */
+function makeCase(
+ lhs: number,
+ rhs: number,
+ truthFunc: (lhs: ScalarValue, rhs: ScalarValue) => boolean
+): Case {
+ // Subnormal float values may be flushed at any time.
+ // https://www.w3.org/TR/WGSL/#floating-point-evaluation
+ const af_lhs = abstractFloat(lhs);
+ const af_rhs = abstractFloat(rhs);
+ const lhs_options = new Set([af_lhs, abstractFloat(flushSubnormalNumberF64(lhs))]);
+ const rhs_options = new Set([af_rhs, abstractFloat(flushSubnormalNumberF64(rhs))]);
+ const expected: Array<ScalarValue> = [];
+ lhs_options.forEach(l => {
+ rhs_options.forEach(r => {
+ const result = bool(truthFunc(l, r));
+ if (!expected.includes(result)) {
+ expected.push(result);
+ }
+ });
+ });
+
+ return { input: [af_lhs, af_rhs], expected: anyOf(...expected) };
+}
+
+export const d = makeCaseCache('binary/af_logical', {
+ equals: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) === (rhs.value as number);
+ };
+
+ return vectorF64Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ not_equals: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) !== (rhs.value as number);
+ };
+
+ return vectorF64Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_than: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) < (rhs.value as number);
+ };
+
+ return vectorF64Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_equals: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) <= (rhs.value as number);
+ };
+
+ return vectorF64Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_than: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) > (rhs.value as number);
+ };
+
+ return vectorF64Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_equals: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) >= (rhs.value as number);
+ };
+
+ return vectorF64Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_comparison.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_comparison.spec.ts
index 5b8b1637b9..3941e12539 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_comparison.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_comparison.spec.ts
@@ -1,110 +1,17 @@
export const description = `
-Execution Tests for the AbstractFloat comparison operations
+Execution Tests for the abstract-float comparison operations
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { anyOf } from '../../../../util/compare.js';
-import {
- abstractFloat,
- bool,
- Scalar,
- TypeAbstractFloat,
- TypeBool,
-} from '../../../../util/conversion.js';
-import { flushSubnormalNumberF64, vectorF64Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, Case, run } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { allInputSources, run } from '../expression.js';
+import { d } from './af_comparison.cache.js';
import { binary } from './binary.js';
export const g = makeTestGroup(GPUTest);
-/**
- * @returns a test case for the provided left hand & right hand values and truth function.
- * Handles quantization and subnormals.
- */
-function makeCase(
- lhs: number,
- rhs: number,
- truthFunc: (lhs: Scalar, rhs: Scalar) => boolean
-): Case {
- // Subnormal float values may be flushed at any time.
- // https://www.w3.org/TR/WGSL/#floating-point-evaluation
- const af_lhs = abstractFloat(lhs);
- const af_rhs = abstractFloat(rhs);
- const lhs_options = new Set([af_lhs, abstractFloat(flushSubnormalNumberF64(lhs))]);
- const rhs_options = new Set([af_rhs, abstractFloat(flushSubnormalNumberF64(rhs))]);
- const expected: Array<Scalar> = [];
- lhs_options.forEach(l => {
- rhs_options.forEach(r => {
- const result = bool(truthFunc(l, r));
- if (!expected.includes(result)) {
- expected.push(result);
- }
- });
- });
-
- return { input: [af_lhs, af_rhs], expected: anyOf(...expected) };
-}
-
-export const d = makeCaseCache('binary/af_logical', {
- equals: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) === (rhs.value as number);
- };
-
- return vectorF64Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- not_equals: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) !== (rhs.value as number);
- };
-
- return vectorF64Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_than: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) < (rhs.value as number);
- };
-
- return vectorF64Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_equals: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) <= (rhs.value as number);
- };
-
- return vectorF64Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_than: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) > (rhs.value as number);
- };
-
- return vectorF64Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_equals: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) >= (rhs.value as number);
- };
-
- return vectorF64Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
-});
-
g.test('equals')
.specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
.desc(
@@ -120,7 +27,14 @@ Accuracy: Correct result
)
.fn(async t => {
const cases = await d.get('equals');
- await run(t, binary('=='), [TypeAbstractFloat, TypeAbstractFloat], TypeBool, t.params, cases);
+ await run(
+ t,
+ binary('=='),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.bool,
+ t.params,
+ cases
+ );
});
g.test('not_equals')
@@ -138,7 +52,14 @@ Accuracy: Correct result
)
.fn(async t => {
const cases = await d.get('not_equals');
- await run(t, binary('!='), [TypeAbstractFloat, TypeAbstractFloat], TypeBool, t.params, cases);
+ await run(
+ t,
+ binary('!='),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.bool,
+ t.params,
+ cases
+ );
});
g.test('less_than')
@@ -156,7 +77,7 @@ Accuracy: Correct result
)
.fn(async t => {
const cases = await d.get('less_than');
- await run(t, binary('<'), [TypeAbstractFloat, TypeAbstractFloat], TypeBool, t.params, cases);
+ await run(t, binary('<'), [Type.abstractFloat, Type.abstractFloat], Type.bool, t.params, cases);
});
g.test('less_equals')
@@ -174,7 +95,14 @@ Accuracy: Correct result
)
.fn(async t => {
const cases = await d.get('less_equals');
- await run(t, binary('<='), [TypeAbstractFloat, TypeAbstractFloat], TypeBool, t.params, cases);
+ await run(
+ t,
+ binary('<='),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.bool,
+ t.params,
+ cases
+ );
});
g.test('greater_than')
@@ -192,7 +120,7 @@ Accuracy: Correct result
)
.fn(async t => {
const cases = await d.get('greater_than');
- await run(t, binary('>'), [TypeAbstractFloat, TypeAbstractFloat], TypeBool, t.params, cases);
+ await run(t, binary('>'), [Type.abstractFloat, Type.abstractFloat], Type.bool, t.params, cases);
});
g.test('greater_equals')
@@ -210,5 +138,12 @@ Accuracy: Correct result
)
.fn(async t => {
const cases = await d.get('greater_equals');
- await run(t, binary('>='), [TypeAbstractFloat, TypeAbstractFloat], TypeBool, t.params, cases);
+ await run(
+ t,
+ binary('>='),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.bool,
+ t.params,
+ cases
+ );
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_division.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_division.cache.ts
new file mode 100644
index 0000000000..ff2fb6a578
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_division.cache.ts
@@ -0,0 +1,57 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF64Range, sparseVectorF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const divisionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ // division has an ulp accuracy, so abstract is only expected to be as accurate as f32
+ return FP.abstract.toVector(v.map(e => FP.f32.divisionInterval(e, s)));
+};
+
+const divisionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ // division has an ulp accuracy, so abstract is only expected to be as accurate as f32
+ return FP.abstract.toVector(v.map(e => FP.f32.divisionInterval(s, e)));
+};
+
+const scalar_cases = {
+ ['scalar']: () => {
+ return FP.abstract.generateScalarPairToIntervalCases(
+ sparseScalarF64Range(),
+ sparseScalarF64Range(),
+ 'finite',
+ // division has an ulp accuracy, so abstract is only expected to be as accurate as f32
+ FP.f32.divisionInterval
+ );
+ },
+};
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`vec${dim}_scalar`]: () => {
+ return FP.abstract.generateVectorScalarToVectorCases(
+ sparseVectorF64Range(dim),
+ sparseScalarF64Range(),
+ 'finite',
+ divisionVectorScalarInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`scalar_vec${dim}`]: () => {
+ return FP.abstract.generateScalarVectorToVectorCases(
+ sparseScalarF64Range(),
+ sparseVectorF64Range(dim),
+ 'finite',
+ divisionScalarVectorInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_division', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_division.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_division.spec.ts
index 4c1765d203..0ebe30b6cc 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_division.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_division.spec.ts
@@ -1,70 +1,17 @@
export const description = `
-Execution Tests for non-matrix AbstractFloat division expression
+Execution Tests for non-matrix abstract-float division 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 { Type } from '../../../../util/conversion.js';
import { onlyConstInputSource, run } from '../expression.js';
-import { abstractBinary } from './binary.js';
-
-const divisionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.divisionInterval(e, s)));
-};
-
-const divisionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.divisionInterval(s, e)));
-};
+import { d } from './af_division.cache.js';
+import { abstractFloatBinary } from './binary.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = {
- ['scalar']: () => {
- return FP.abstract.generateScalarPairToIntervalCases(
- sparseF64Range(),
- sparseF64Range(),
- 'finite',
- FP.abstract.divisionInterval
- );
- },
-};
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`vec${dim}_scalar`]: () => {
- return FP.abstract.generateVectorScalarToVectorCases(
- sparseVectorF64Range(dim),
- sparseF64Range(),
- 'finite',
- divisionVectorScalarInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`scalar_vec${dim}`]: () => {
- return FP.abstract.generateScalarVectorToVectorCases(
- sparseF64Range(),
- sparseVectorF64Range(dim),
- 'finite',
- divisionScalarVectorInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/af_division', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -78,9 +25,9 @@ Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
const cases = await d.get('scalar');
await run(
t,
- abstractBinary('/'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('/'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -101,9 +48,9 @@ Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases
await run(
t,
- abstractBinary('/'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('/'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -123,9 +70,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`vec${dim}_scalar`);
await run(
t,
- abstractBinary('/'),
- [TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('/'),
+ [Type.vec(dim, Type.abstractFloat), Type.abstractFloat],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
@@ -145,9 +92,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`scalar_vec${dim}`);
await run(
t,
- abstractBinary('/'),
- [TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('/'),
+ [Type.abstractFloat, Type.vec(dim, Type.abstractFloat)],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_addition.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_addition.cache.ts
new file mode 100644
index 0000000000..e89250f57b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_addition.cache.ts
@@ -0,0 +1,26 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF64Range } from '../../../../util/math.js';
+import { selectNCases } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR
+const mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).map(rows => ({
+ [`mat${cols}x${rows}`]: () => {
+ return selectNCases(
+ 'binary/af_matrix_addition',
+ 50,
+ FP.abstract.generateMatrixPairToMatrixCases(
+ sparseMatrixF64Range(cols, rows),
+ sparseMatrixF64Range(cols, rows),
+ 'finite',
+ FP.abstract.additionMatrixMatrixInterval
+ )
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_matrix_addition', mat_cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_addition.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_addition.spec.ts
index 86bddec894..49c746c53e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_addition.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_addition.spec.ts
@@ -1,37 +1,17 @@
export const description = `
-Execution Tests for matrix AbstractFloat addition expressions
+Execution Tests for matrix abstract-float 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 { Type } from '../../../../util/conversion.js';
import { onlyConstInputSource, run } from '../expression.js';
-import { abstractBinary } from './binary.js';
+import { d } from './af_matrix_addition.cache.js';
+import { abstractFloatBinary } from './binary.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR
-const mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).map(rows => ({
- [`mat${cols}x${rows}`]: () => {
- return FP.abstract.generateMatrixPairToMatrixCases(
- sparseMatrixF64Range(cols, rows),
- sparseMatrixF64Range(cols, rows),
- 'finite',
- FP.abstract.additionMatrixMatrixInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/af_matrix_addition', mat_cases);
-
g.test('matrix')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -52,9 +32,9 @@ Accuracy: Correctly rounded
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),
+ abstractFloatBinary('+'),
+ [Type.mat(cols, rows, Type.abstractFloat), Type.mat(cols, rows, Type.abstractFloat)],
+ Type.mat(cols, rows, Type.abstractFloat),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_matrix_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_matrix_multiplication.cache.ts
new file mode 100644
index 0000000000..78512fbd0d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_matrix_multiplication.cache.ts
@@ -0,0 +1,29 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF64Range } from '../../../../util/math.js';
+import { selectNCases } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matKxR_matCxK
+const mat_mat_cases = ([2, 3, 4] as const)
+ .flatMap(k =>
+ ([2, 3, 4] as const).flatMap(cols =>
+ ([2, 3, 4] as const).map(rows => ({
+ [`mat${k}x${rows}_mat${cols}x${k}`]: () => {
+ return selectNCases(
+ 'binary/af_matrix_matrix_multiplication',
+ 10,
+ FP.abstract.generateMatrixPairToMatrixCases(
+ sparseMatrixF64Range(k, rows),
+ sparseMatrixF64Range(cols, k),
+ 'finite',
+ // Matrix-matrix multiplication has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP.f32.multiplicationMatrixMatrixInterval
+ )
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_matrix_matrix_multiplication', mat_mat_cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_matrix_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_matrix_multiplication.spec.ts
new file mode 100644
index 0000000000..9320fb1226
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_matrix_multiplication.spec.ts
@@ -0,0 +1,45 @@
+export const description = `
+Execution Tests for matrix-matrix AbstractFloat multiplication expression
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { Type } from '../../../../util/conversion.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { d } from './af_matrix_matrix_multiplication.cache.js';
+import { abstractFloatBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('matrix_matrix')
+ .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
+ .desc(
+ `
+Expression: x * y, where x is a matrix and y is a matrix
+Accuracy: Correctly rounded
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('common_dim', [2, 3, 4] as const)
+ .combine('x_rows', [2, 3, 4] as const)
+ .combine('y_cols', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const x_cols = t.params.common_dim;
+ const x_rows = t.params.x_rows;
+ const y_cols = t.params.y_cols;
+ const y_rows = t.params.common_dim;
+
+ const cases = await d.get(`mat${x_cols}x${x_rows}_mat${y_cols}x${y_rows}`);
+ await run(
+ t,
+ abstractFloatBinary('*'),
+ [Type.mat(x_cols, x_rows, Type.abstractFloat), Type.mat(y_cols, y_rows, Type.abstractFloat)],
+ Type.mat(y_cols, x_rows, Type.abstractFloat),
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_scalar_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_scalar_multiplication.cache.ts
new file mode 100644
index 0000000000..9106362970
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_scalar_multiplication.cache.ts
@@ -0,0 +1,49 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF64Range, sparseScalarF64Range } from '../../../../util/math.js';
+import { selectNCases } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_scalar
+const mat_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).map(rows => ({
+ [`mat${cols}x${rows}_scalar`]: () => {
+ return selectNCases(
+ 'binary/af_matrix_scalar_multiplication_mat_scalar',
+ 50,
+ FP.abstract.generateMatrixScalarToMatrixCases(
+ sparseMatrixF64Range(cols, rows),
+ sparseScalarF64Range(),
+ 'finite',
+ FP.abstract.multiplicationMatrixScalarInterval
+ )
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: scalar_matCxR
+const scalar_mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).map(rows => ({
+ [`scalar_mat${cols}x${rows}`]: () => {
+ return selectNCases(
+ 'binary/af_matrix_scalar_multiplication_scalar_mat',
+ 50,
+ FP.abstract.generateScalarMatrixToMatrixCases(
+ sparseScalarF64Range(),
+ sparseMatrixF64Range(cols, rows),
+ 'finite',
+ FP.abstract.multiplicationScalarMatrixInterval
+ )
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_matrix_scalar_multiplication', {
+ ...mat_scalar_cases,
+ ...scalar_mat_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_scalar_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_scalar_multiplication.spec.ts
new file mode 100644
index 0000000000..c6faabbc84
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_scalar_multiplication.spec.ts
@@ -0,0 +1,69 @@
+export const description = `
+Execution Tests for matrix-scalar and scalar-matrix AbstractFloat multiplication expression
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { Type } from '../../../../util/conversion.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { d } from './af_matrix_scalar_multiplication.cache.js';
+import { abstractFloatBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('matrix_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
+ .desc(
+ `
+Expression: x * y, where x is a matrix and y is a scalar
+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}_scalar`);
+ await run(
+ t,
+ abstractFloatBinary('*'),
+ [Type.mat(cols, rows, Type.abstractFloat), Type.abstractFloat],
+ Type.mat(cols, rows, Type.abstractFloat),
+ t.params,
+ cases
+ );
+ });
+
+g.test('scalar_matrix')
+ .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
+ .desc(
+ `
+Expression: x * y, where x is a scalar and y is a matrix
+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(`scalar_mat${cols}x${rows}`);
+ await run(
+ t,
+ abstractFloatBinary('*'),
+ [Type.abstractFloat, Type.mat(cols, rows, Type.abstractFloat)],
+ Type.mat(cols, rows, Type.abstractFloat),
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.cache.ts
new file mode 100644
index 0000000000..c3e5e856dc
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.cache.ts
@@ -0,0 +1,26 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF64Range } from '../../../../util/math.js';
+import { selectNCases } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR
+const mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).map(rows => ({
+ [`mat${cols}x${rows}`]: () => {
+ return selectNCases(
+ 'binary/af_matrix_subtraction',
+ 50,
+ FP.abstract.generateMatrixPairToMatrixCases(
+ sparseMatrixF64Range(cols, rows),
+ sparseMatrixF64Range(cols, rows),
+ 'finite',
+ FP.abstract.subtractionMatrixMatrixInterval
+ )
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_matrix_subtraction', mat_cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.spec.ts
index 849c11611f..9b240fdee9 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_subtraction.spec.ts
@@ -1,37 +1,17 @@
export const description = `
-Execution Tests for matrix AbstractFloat subtraction expression
+Execution Tests for matrix abstract-float 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 { Type } from '../../../../util/conversion.js';
import { onlyConstInputSource, run } from '../expression.js';
-import { abstractBinary } from './binary.js';
+import { d } from './af_matrix_subtraction.cache.js';
+import { abstractFloatBinary } from './binary.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR
-const mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).map(rows => ({
- [`mat${cols}x${rows}`]: () => {
- return FP.abstract.generateMatrixPairToMatrixCases(
- sparseMatrixF64Range(cols, rows),
- sparseMatrixF64Range(cols, rows),
- 'finite',
- FP.abstract.subtractionMatrixMatrixInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/af_matrix_subtraction', mat_cases);
-
g.test('matrix')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -52,9 +32,9 @@ Accuracy: Correctly rounded
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),
+ abstractFloatBinary('-'),
+ [Type.mat(cols, rows, Type.abstractFloat), Type.mat(cols, rows, Type.abstractFloat)],
+ Type.mat(cols, rows, Type.abstractFloat),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_vector_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_vector_multiplication.cache.ts
new file mode 100644
index 0000000000..4578eb0ce4
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_vector_multiplication.cache.ts
@@ -0,0 +1,51 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF64Range, sparseVectorF64Range } from '../../../../util/math.js';
+import { selectNCases } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_vecC
+const mat_vec_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).map(rows => ({
+ [`mat${cols}x${rows}_vec${cols}`]: () => {
+ return selectNCases(
+ 'binary/af_matrix_vector_multiplication_mat_vec',
+ 50,
+ FP.abstract.generateMatrixVectorToVectorCases(
+ sparseMatrixF64Range(cols, rows),
+ sparseVectorF64Range(cols),
+ 'finite',
+ // Matrix-vector multiplication has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP.f32.multiplicationMatrixVectorInterval
+ )
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: vecR_matCxR
+const vec_mat_cases = ([2, 3, 4] as const)
+ .flatMap(rows =>
+ ([2, 3, 4] as const).map(cols => ({
+ [`vec${rows}_mat${cols}x${rows}`]: () => {
+ return selectNCases(
+ 'binary/af_matrix_vector_multiplication_vec_mat',
+ 50,
+ FP.abstract.generateVectorMatrixToVectorCases(
+ sparseVectorF64Range(rows),
+ sparseMatrixF64Range(cols, rows),
+ 'finite',
+ // Vector-matrix multiplication has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP.f32.multiplicationVectorMatrixInterval
+ )
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_matrix_vector_multiplication', {
+ ...mat_vec_cases,
+ ...vec_mat_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_vector_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_vector_multiplication.spec.ts
new file mode 100644
index 0000000000..5db78f8369
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_matrix_vector_multiplication.spec.ts
@@ -0,0 +1,69 @@
+export const description = `
+Execution Tests for matrix-vector and vector-matrix AbstractFloat multiplication expression
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { Type } from '../../../../util/conversion.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { d } from './af_matrix_vector_multiplication.cache.js';
+import { abstractFloatBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('matrix_vector')
+ .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
+ .desc(
+ `
+Expression: x * y, where x is a matrix and y is a vector
+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}_vec${cols}`);
+ await run(
+ t,
+ abstractFloatBinary('*'),
+ [Type.mat(cols, rows, Type.abstractFloat), Type.vec(cols, Type.abstractFloat)],
+ Type.vec(rows, Type.abstractFloat),
+ t.params,
+ cases
+ );
+ });
+
+g.test('vector_matrix')
+ .specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
+ .desc(
+ `
+Expression: x * y, where x is a vector and y is is a matrix
+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(`vec${rows}_mat${cols}x${rows}`);
+ await run(
+ t,
+ abstractFloatBinary('*'),
+ [Type.vec(rows, Type.abstractFloat), Type.mat(cols, rows, Type.abstractFloat)],
+ Type.vec(cols, Type.abstractFloat),
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_multiplication.cache.ts
new file mode 100644
index 0000000000..e111682cd2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_multiplication.cache.ts
@@ -0,0 +1,54 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF64Range, sparseVectorF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const multiplicationVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.abstract.toVector(v.map(e => FP.abstract.multiplicationInterval(e, s)));
+};
+
+const multiplicationScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.abstract.toVector(v.map(e => FP.abstract.multiplicationInterval(s, e)));
+};
+
+const scalar_cases = {
+ ['scalar']: () => {
+ return FP.abstract.generateScalarPairToIntervalCases(
+ sparseScalarF64Range(),
+ sparseScalarF64Range(),
+ 'finite',
+ FP.abstract.multiplicationInterval
+ );
+ },
+};
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`vec${dim}_scalar`]: () => {
+ return FP.abstract.generateVectorScalarToVectorCases(
+ sparseVectorF64Range(dim),
+ sparseScalarF64Range(),
+ 'finite',
+ multiplicationVectorScalarInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`scalar_vec${dim}`]: () => {
+ return FP.abstract.generateScalarVectorToVectorCases(
+ sparseScalarF64Range(),
+ sparseVectorF64Range(dim),
+ 'finite',
+ multiplicationScalarVectorInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_multiplication', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts
index 6b15812703..405de758bd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_multiplication.spec.ts
@@ -1,70 +1,17 @@
export const description = `
-Execution Tests for non-matrix AbstractFloat multiplication expression
+Execution Tests for non-matrix abstract-float 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 { Type } from '../../../../util/conversion.js';
import { onlyConstInputSource, run } from '../expression.js';
-import { abstractBinary } from './binary.js';
-
-const multiplicationVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.multiplicationInterval(e, s)));
-};
-
-const multiplicationScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.multiplicationInterval(s, e)));
-};
+import { d } from './af_multiplication.cache.js';
+import { abstractFloatBinary } from './binary.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = {
- ['scalar']: () => {
- return FP.abstract.generateScalarPairToIntervalCases(
- sparseF64Range(),
- sparseF64Range(),
- 'finite',
- FP.abstract.multiplicationInterval
- );
- },
-};
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`vec${dim}_scalar`]: () => {
- return FP.abstract.generateVectorScalarToVectorCases(
- sparseVectorF64Range(dim),
- sparseF64Range(),
- 'finite',
- multiplicationVectorScalarInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`scalar_vec${dim}`]: () => {
- return FP.abstract.generateScalarVectorToVectorCases(
- sparseF64Range(),
- sparseVectorF64Range(dim),
- 'finite',
- multiplicationScalarVectorInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/af_multiplication', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -78,9 +25,9 @@ Accuracy: Correctly rounded
const cases = await d.get('scalar');
await run(
t,
- abstractBinary('*'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('*'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -101,9 +48,9 @@ Accuracy: Correctly rounded
const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases
await run(
t,
- abstractBinary('*'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('*'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -123,9 +70,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`vec${dim}_scalar`);
await run(
t,
- abstractBinary('*'),
- [TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('*'),
+ [Type.vec(dim, Type.abstractFloat), Type.abstractFloat],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
@@ -145,9 +92,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`scalar_vec${dim}`);
await run(
t,
- abstractBinary('*'),
- [TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('*'),
+ [Type.abstractFloat, Type.vec(dim, Type.abstractFloat)],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_remainder.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_remainder.cache.ts
new file mode 100644
index 0000000000..4817298167
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_remainder.cache.ts
@@ -0,0 +1,57 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF64Range, sparseVectorF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const remainderVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ // remainder has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ return FP.abstract.toVector(v.map(e => FP.f32.remainderInterval(e, s)));
+};
+
+const remainderScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ // remainder has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ return FP.abstract.toVector(v.map(e => FP.f32.remainderInterval(s, e)));
+};
+
+const scalar_cases = {
+ ['scalar']: () => {
+ return FP.abstract.generateScalarPairToIntervalCases(
+ sparseScalarF64Range(),
+ sparseScalarF64Range(),
+ 'finite',
+ // remainder has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP.f32.remainderInterval
+ );
+ },
+};
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`vec${dim}_scalar`]: () => {
+ return FP.abstract.generateVectorScalarToVectorCases(
+ sparseVectorF64Range(dim),
+ sparseScalarF64Range(),
+ 'finite',
+ remainderVectorScalarInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`scalar_vec${dim}`]: () => {
+ return FP.abstract.generateScalarVectorToVectorCases(
+ sparseScalarF64Range(),
+ sparseVectorF64Range(dim),
+ 'finite',
+ remainderScalarVectorInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_remainder', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_remainder.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_remainder.spec.ts
index b4ce930bdb..d743f85ed6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_remainder.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_remainder.spec.ts
@@ -4,67 +4,14 @@ Execution Tests for non-matrix abstract float remainder 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 { Type } from '../../../../util/conversion.js';
import { onlyConstInputSource, run } from '../expression.js';
-import { abstractBinary } from './binary.js';
-
-const remainderVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.remainderInterval(e, s)));
-};
-
-const remainderScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.remainderInterval(s, e)));
-};
+import { d } from './af_remainder.cache.js';
+import { abstractFloatBinary } from './binary.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = {
- ['scalar']: () => {
- return FP.abstract.generateScalarPairToIntervalCases(
- sparseF64Range(),
- sparseF64Range(),
- 'finite',
- FP.abstract.remainderInterval
- );
- },
-};
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`vec${dim}_scalar`]: () => {
- return FP.abstract.generateVectorScalarToVectorCases(
- sparseVectorF64Range(dim),
- sparseF64Range(),
- 'finite',
- remainderVectorScalarInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`scalar_vec${dim}`]: () => {
- return FP.abstract.generateScalarVectorToVectorCases(
- sparseF64Range(),
- sparseVectorF64Range(dim),
- 'finite',
- remainderScalarVectorInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/af_remainder', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -78,9 +25,9 @@ Accuracy: Derived from x - y * trunc(x/y)
const cases = await d.get('scalar');
await run(
t,
- abstractBinary('%'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('%'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -101,9 +48,9 @@ Accuracy: Derived from x - y * trunc(x/y)
const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases
await run(
t,
- abstractBinary('%'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('%'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -123,9 +70,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`vec${dim}_scalar`);
await run(
t,
- abstractBinary('%'),
- [TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('%'),
+ [Type.vec(dim, Type.abstractFloat), Type.abstractFloat],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
@@ -145,9 +92,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`scalar_vec${dim}`);
await run(
t,
- abstractBinary('%'),
- [TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('%'),
+ [Type.abstractFloat, Type.vec(dim, Type.abstractFloat)],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_subtraction.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_subtraction.cache.ts
new file mode 100644
index 0000000000..ea5107e143
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_subtraction.cache.ts
@@ -0,0 +1,54 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF64Range, sparseVectorF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const subtractionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.abstract.toVector(v.map(e => FP.abstract.subtractionInterval(e, s)));
+};
+
+const subtractionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.abstract.toVector(v.map(e => FP.abstract.subtractionInterval(s, e)));
+};
+
+const scalar_cases = {
+ ['scalar']: () => {
+ return FP.abstract.generateScalarPairToIntervalCases(
+ sparseScalarF64Range(),
+ sparseScalarF64Range(),
+ 'finite',
+ FP.abstract.subtractionInterval
+ );
+ },
+};
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`vec${dim}_scalar`]: () => {
+ return FP.abstract.generateVectorScalarToVectorCases(
+ sparseVectorF64Range(dim),
+ sparseScalarF64Range(),
+ 'finite',
+ subtractionVectorScalarInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`scalar_vec${dim}`]: () => {
+ return FP.abstract.generateScalarVectorToVectorCases(
+ sparseScalarF64Range(),
+ sparseVectorF64Range(dim),
+ 'finite',
+ subtractionScalarVectorInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/af_subtraction', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts
index 00dc66feb9..2874a744da 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/af_subtraction.spec.ts
@@ -1,70 +1,17 @@
export const description = `
-Execution Tests for non-matrix AbstractFloat subtraction expression
+Execution Tests for non-matrix abstract-float 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 { Type } from '../../../../util/conversion.js';
import { onlyConstInputSource, run } from '../expression.js';
-import { abstractBinary } from './binary.js';
-
-const subtractionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.subtractionInterval(e, s)));
-};
-
-const subtractionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.abstract.toVector(v.map(e => FP.abstract.subtractionInterval(s, e)));
-};
+import { d } from './af_subtraction.cache.js';
+import { abstractFloatBinary } from './binary.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = {
- ['scalar']: () => {
- return FP.abstract.generateScalarPairToIntervalCases(
- sparseF64Range(),
- sparseF64Range(),
- 'finite',
- FP.abstract.subtractionInterval
- );
- },
-};
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`vec${dim}_scalar`]: () => {
- return FP.abstract.generateVectorScalarToVectorCases(
- sparseVectorF64Range(dim),
- sparseF64Range(),
- 'finite',
- subtractionVectorScalarInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .map(dim => ({
- [`scalar_vec${dim}`]: () => {
- return FP.abstract.generateScalarVectorToVectorCases(
- sparseF64Range(),
- sparseVectorF64Range(dim),
- 'finite',
- subtractionScalarVectorInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/af_subtraction', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -78,9 +25,9 @@ Accuracy: Correctly rounded
const cases = await d.get('scalar');
await run(
t,
- abstractBinary('-'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('-'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -101,9 +48,9 @@ Accuracy: Correctly rounded
const cases = await d.get('scalar'); // Using vectorize to generate vector cases based on scalar cases
await run(
t,
- abstractBinary('-'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBinary('-'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -123,9 +70,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`vec${dim}_scalar`);
await run(
t,
- abstractBinary('-'),
- [TypeVec(dim, TypeAbstractFloat), TypeAbstractFloat],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('-'),
+ [Type.vec(dim, Type.abstractFloat), Type.abstractFloat],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
@@ -145,9 +92,9 @@ Accuracy: Correctly rounded
const cases = await d.get(`scalar_vec${dim}`);
await run(
t,
- abstractBinary('-'),
- [TypeAbstractFloat, TypeVec(dim, TypeAbstractFloat)],
- TypeVec(dim, TypeAbstractFloat),
+ abstractFloatBinary('-'),
+ [Type.abstractFloat, Type.vec(dim, Type.abstractFloat)],
+ Type.vec(dim, Type.abstractFloat),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_arithmetic.cache.ts
new file mode 100644
index 0000000000..48300e8013
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_arithmetic.cache.ts
@@ -0,0 +1,145 @@
+import { kValue } from '../../../../util/constants.js';
+import { sparseI64Range, vectorI64Range } from '../../../../util/math.js';
+import {
+ generateBinaryToI64Cases,
+ generateI64VectorBinaryToVectorCases,
+ generateVectorI64BinaryToVectorCases,
+} from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+function ai_add(x: bigint, y: bigint): bigint | undefined {
+ const result = x + y;
+ return !kValue.i64.isOOB(result) ? result : undefined;
+}
+
+function ai_div(x: bigint, y: bigint): bigint | undefined {
+ if (y === 0n) return undefined;
+ if (x === kValue.i64.negative.min && y === -1n) return undefined;
+ const result = x / y;
+ return !kValue.i64.isOOB(result) ? result : undefined;
+}
+
+function ai_mul(x: bigint, y: bigint): bigint | undefined {
+ const result = x * y;
+ return !kValue.i64.isOOB(result) ? result : undefined;
+}
+
+function ai_rem(x: bigint, y: bigint): bigint | undefined {
+ if (y === 0n) return undefined;
+ if (x === kValue.i64.negative.min && y === -1n) return undefined;
+ const result = x % y;
+ return !kValue.i64.isOOB(result) ? result : undefined;
+}
+
+function ai_sub(x: bigint, y: bigint): bigint | undefined {
+ const result = x - y;
+ return !kValue.i64.isOOB(result) ? result : undefined;
+}
+
+export const d = makeCaseCache('binary/ai_arithmetic', {
+ addition: () => {
+ return generateBinaryToI64Cases(sparseI64Range(), sparseI64Range(), ai_add);
+ },
+ addition_scalar_vector2: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(2), ai_add);
+ },
+ addition_scalar_vector3: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(3), ai_add);
+ },
+ addition_scalar_vector4: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(4), ai_add);
+ },
+ addition_vector2_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(2), sparseI64Range(), ai_add);
+ },
+ addition_vector3_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(3), sparseI64Range(), ai_add);
+ },
+ addition_vector4_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(4), sparseI64Range(), ai_add);
+ },
+ division: () => {
+ return generateBinaryToI64Cases(sparseI64Range(), sparseI64Range(), ai_div);
+ },
+ division_scalar_vector2: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(2), ai_div);
+ },
+ division_scalar_vector3: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(3), ai_div);
+ },
+ division_scalar_vector4: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(4), ai_div);
+ },
+ division_vector2_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(2), sparseI64Range(), ai_div);
+ },
+ division_vector3_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(3), sparseI64Range(), ai_div);
+ },
+ division_vector4_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(4), sparseI64Range(), ai_div);
+ },
+ multiplication: () => {
+ return generateBinaryToI64Cases(sparseI64Range(), sparseI64Range(), ai_mul);
+ },
+ multiplication_scalar_vector2: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(2), ai_mul);
+ },
+ multiplication_scalar_vector3: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(3), ai_mul);
+ },
+ multiplication_scalar_vector4: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(4), ai_mul);
+ },
+ multiplication_vector2_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(2), sparseI64Range(), ai_mul);
+ },
+ multiplication_vector3_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(3), sparseI64Range(), ai_mul);
+ },
+ multiplication_vector4_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(4), sparseI64Range(), ai_mul);
+ },
+ remainder: () => {
+ return generateBinaryToI64Cases(sparseI64Range(), sparseI64Range(), ai_rem);
+ },
+ remainder_scalar_vector2: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(2), ai_rem);
+ },
+ remainder_scalar_vector3: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(3), ai_rem);
+ },
+ remainder_scalar_vector4: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(4), ai_rem);
+ },
+ remainder_vector2_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(2), sparseI64Range(), ai_rem);
+ },
+ remainder_vector3_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(3), sparseI64Range(), ai_rem);
+ },
+ remainder_vector4_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(4), sparseI64Range(), ai_rem);
+ },
+ subtraction: () => {
+ return generateBinaryToI64Cases(sparseI64Range(), sparseI64Range(), ai_sub);
+ },
+ subtraction_scalar_vector2: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(2), ai_sub);
+ },
+ subtraction_scalar_vector3: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(3), ai_sub);
+ },
+ subtraction_scalar_vector4: () => {
+ return generateI64VectorBinaryToVectorCases(sparseI64Range(), vectorI64Range(4), ai_sub);
+ },
+ subtraction_vector2_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(2), sparseI64Range(), ai_sub);
+ },
+ subtraction_vector3_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(3), sparseI64Range(), ai_sub);
+ },
+ subtraction_vector4_scalar: () => {
+ return generateVectorI64BinaryToVectorCases(vectorI64Range(4), sparseI64Range(), ai_sub);
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_arithmetic.spec.ts
new file mode 100644
index 0000000000..ef211af3ed
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_arithmetic.spec.ts
@@ -0,0 +1,303 @@
+export const description = `
+Execution Tests for the abstract int arithmetic binary expression operations
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { Type } from '../../../../util/conversion.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { d } from './ai_arithmetic.cache.js';
+import { abstractIntBinary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('addition')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x + y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('addition');
+ await run(
+ t,
+ abstractIntBinary('+'),
+ [Type.abstractInt, Type.abstractInt],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
+
+g.test('addition_scalar_vector')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x + y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_rhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`addition_scalar_vector${vec_size}`);
+ await run(t, abstractIntBinary('+'), [Type.abstractInt, vec_type], vec_type, t.params, cases);
+ });
+
+g.test('addition_vector_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x + y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_lhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`addition_vector${vec_size}_scalar`);
+ await run(t, abstractIntBinary('+'), [vec_type, Type.abstractInt], vec_type, t.params, cases);
+ });
+
+g.test('division')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x / y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('division');
+ await run(
+ t,
+ abstractIntBinary('/'),
+ [Type.abstractInt, Type.abstractInt],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
+
+g.test('division_scalar_vector')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x / y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_rhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`division_scalar_vector${vec_size}`);
+ await run(t, abstractIntBinary('/'), [Type.abstractInt, vec_type], vec_type, t.params, cases);
+ });
+
+g.test('division_vector_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x / y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_lhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`division_vector${vec_size}_scalar`);
+ await run(t, abstractIntBinary('/'), [vec_type, Type.abstractInt], vec_type, t.params, cases);
+ });
+
+g.test('multiplication')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x * y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('multiplication');
+ await run(
+ t,
+ abstractIntBinary('*'),
+ [Type.abstractInt, Type.abstractInt],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
+
+g.test('multiplication_scalar_vector')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x * y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_rhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`multiplication_scalar_vector${vec_size}`);
+ await run(t, abstractIntBinary('*'), [Type.abstractInt, vec_type], vec_type, t.params, cases);
+ });
+
+g.test('multiplication_vector_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x * y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_lhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`multiplication_vector${vec_size}_scalar`);
+ await run(t, abstractIntBinary('*'), [vec_type, Type.abstractInt], vec_type, t.params, cases);
+ });
+
+g.test('remainder')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x % y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('remainder');
+ await run(
+ t,
+ abstractIntBinary('%'),
+ [Type.abstractInt, Type.abstractInt],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
+
+g.test('remainder_scalar_vector')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x % y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_rhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`remainder_scalar_vector${vec_size}`);
+ await run(t, abstractIntBinary('%'), [Type.abstractInt, vec_type], vec_type, t.params, cases);
+ });
+
+g.test('remainder_vector_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x % y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_lhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`remainder_vector${vec_size}_scalar`);
+ await run(t, abstractIntBinary('%'), [vec_type, Type.abstractInt], vec_type, t.params, cases);
+ });
+
+g.test('subtraction')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x - y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('subtraction');
+ await run(
+ t,
+ abstractIntBinary('-'),
+ [Type.abstractInt, Type.abstractInt],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
+
+g.test('subtraction_scalar_vector')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x - y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_rhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_rhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`subtraction_scalar_vector${vec_size}`);
+ await run(t, abstractIntBinary('-'), [Type.abstractInt, vec_type], vec_type, t.params, cases);
+ });
+
+g.test('subtraction_vector_scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: x - y
+`
+ )
+ .params(u =>
+ u.combine('inputSource', onlyConstInputSource).combine('vectorize_lhs', [2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const vec_size = t.params.vectorize_lhs;
+ const vec_type = Type.vec(vec_size, Type.abstractInt);
+ const cases = await d.get(`subtraction_vector${vec_size}_scalar`);
+ await run(t, abstractIntBinary('-'), [vec_type, Type.abstractInt], vec_type, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_comparison.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_comparison.spec.ts
new file mode 100644
index 0000000000..899e651054
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/ai_comparison.spec.ts
@@ -0,0 +1,124 @@
+export const description = `
+Execution Tests for the abstract-int comparison expressions
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { bool, abstractInt, Type } from '../../../../util/conversion.js';
+import { vectorI64Range } from '../../../../util/math.js';
+import { Case } from '../case.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { binary } from './binary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+/**
+ * @returns a test case for the provided left hand & right hand values and
+ * expected boolean result.
+ */
+function makeCase(lhs: bigint, rhs: bigint, expected_answer: boolean): Case {
+ return { input: [abstractInt(lhs), abstractInt(rhs)], expected: bool(expected_answer) };
+}
+
+g.test('equals')
+ .specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
+ .desc(
+ `
+Expression: x == y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = vectorI64Range(2).map(v => makeCase(v[0], v[1], v[0] === v[1]));
+ await run(t, binary('=='), [Type.abstractInt, Type.abstractInt], Type.bool, t.params, cases);
+ });
+
+g.test('not_equals')
+ .specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
+ .desc(
+ `
+Expression: x != y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = vectorI64Range(2).map(v => makeCase(v[0], v[1], v[0] !== v[1]));
+ await run(t, binary('!='), [Type.abstractInt, Type.abstractInt], Type.bool, t.params, cases);
+ });
+
+g.test('less_than')
+ .specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
+ .desc(
+ `
+Expression: x < y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = vectorI64Range(2).map(v => makeCase(v[0], v[1], v[0] < v[1]));
+ await run(t, binary('<'), [Type.abstractInt, Type.abstractInt], Type.bool, t.params, cases);
+ });
+
+g.test('less_equals')
+ .specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
+ .desc(
+ `
+Expression: x <= y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = vectorI64Range(2).map(v => makeCase(v[0], v[1], v[0] <= v[1]));
+ await run(t, binary('<='), [Type.abstractInt, Type.abstractInt], Type.bool, t.params, cases);
+ });
+
+g.test('greater_than')
+ .specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
+ .desc(
+ `
+Expression: x > y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = vectorI64Range(2).map(v => makeCase(v[0], v[1], v[0] > v[1]));
+ await run(t, binary('>'), [Type.abstractInt, Type.abstractInt], Type.bool, t.params, cases);
+ });
+
+g.test('greater_equals')
+ .specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
+ .desc(
+ `
+Expression: x >= y
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = vectorI64Range(2).map(v => makeCase(v[0], v[1], v[0] >= v[1]));
+ await run(t, binary('>='), [Type.abstractInt, Type.abstractInt], Type.bool, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/binary.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/binary.ts
index f0b01b839b..4df0c67d78 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/binary.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/binary.ts
@@ -3,6 +3,7 @@ import {
basicExpressionBuilder,
compoundAssignmentBuilder,
abstractFloatShaderBuilder,
+ abstractIntShaderBuilder,
} from '../expression.js';
/* @returns a ShaderBuilder that evaluates a binary operation */
@@ -16,6 +17,11 @@ export function compoundBinary(op: string): ShaderBuilder {
}
/* @returns a ShaderBuilder that evaluates a binary operation that returns AbstractFloats */
-export function abstractBinary(op: string): ShaderBuilder {
+export function abstractFloatBinary(op: string): ShaderBuilder {
return abstractFloatShaderBuilder(values => `(${values.map(v => `(${v})`).join(op)})`);
}
+
+/* @returns a ShaderBuilder that evaluates a binary operation that returns AbstractFloats */
+export function abstractIntBinary(op: string): ShaderBuilder {
+ return abstractIntShaderBuilder(values => `(${values.map(v => `(${v})`).join(op)})`);
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise.spec.ts
index 0d8d775352..b6e9a45e9d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise.spec.ts
@@ -3,59 +3,171 @@ Execution Tests for the bitwise binary expression operations
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { assert } from '../../../../../common/util/util.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { i32, scalarType, u32 } from '../../../../util/conversion.js';
-import { allInputSources, run } from '../expression.js';
+import {
+ abstractIntBits,
+ i32Bits,
+ ScalarValue,
+ scalarType,
+ u32Bits,
+} from '../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../expression.js';
-import { binary, compoundBinary } from './binary.js';
+import { abstractIntBinary, binary, compoundBinary } from './binary.js';
export const g = makeTestGroup(GPUTest);
-function makeBitwiseOrCases(inputType: string) {
- const V = inputType === 'i32' ? i32 : u32;
- const cases = [
- // Static patterns
+/**
+ * Collection of functions and values required to implement bitwise tests for a
+ * specific scalar type
+ */
+interface ScalarImpl {
+ // builder is a mostly a wrapper around type builders like 'i32Bits' that
+ // handles the (number|bigint) type check.
+ builder: (bits: bigint | number) => ScalarValue;
+ size: 32 | 64;
+}
+
+const kScalarImpls = {
+ i32: {
+ builder: (bits: bigint | number): ScalarValue => {
+ assert(typeof bits === 'number');
+ return i32Bits(bits);
+ },
+ size: 32,
+ } as ScalarImpl,
+ u32: {
+ builder: (bits: bigint | number): ScalarValue => {
+ assert(typeof bits === 'number');
+ return u32Bits(bits);
+ },
+ size: 32,
+ } as ScalarImpl,
+ 'abstract-int': {
+ builder: (bits: bigint | number): ScalarValue => {
+ assert(typeof bits === 'bigint');
+ return abstractIntBits(bits);
+ },
+ size: 64,
+ } as ScalarImpl,
+};
+
+/** Wrapper for converting from input type strings to the appropriate implementation */
+function scalarImplForInputType(inputType: string): ScalarImpl {
+ assert(inputType === 'i32' || inputType === 'u32' || inputType === 'abstract-int');
+ return kScalarImpls[inputType];
+}
+
+/** Manually calculated bitwise-or cases used a check that the CTS test is correct */
+const kBitwiseOrStaticPatterns = {
+ 32: [
{
- input: [V(0b00000000000000000000000000000000), V(0b00000000000000000000000000000000)],
- expected: V(0b00000000000000000000000000000000),
+ input: [0b00000000000000000000000000000000, 0b00000000000000000000000000000000],
+ expected: 0b00000000000000000000000000000000,
},
{
- input: [V(0b11111111111111111111111111111111), V(0b00000000000000000000000000000000)],
- expected: V(0b11111111111111111111111111111111),
+ input: [0b11111111111111111111111111111111, 0b00000000000000000000000000000000],
+ expected: 0b11111111111111111111111111111111,
},
{
- input: [V(0b00000000000000000000000000000000), V(0b11111111111111111111111111111111)],
- expected: V(0b11111111111111111111111111111111),
+ input: [0b00000000000000000000000000000000, 0b11111111111111111111111111111111],
+ expected: 0b11111111111111111111111111111111,
},
{
- input: [V(0b11111111111111111111111111111111), V(0b11111111111111111111111111111111)],
- expected: V(0b11111111111111111111111111111111),
+ input: [0b11111111111111111111111111111111, 0b11111111111111111111111111111111],
+ expected: 0b11111111111111111111111111111111,
},
{
- input: [V(0b10100100010010100100010010100100), V(0b00000000000000000000000000000000)],
- expected: V(0b10100100010010100100010010100100),
+ input: [0b10100100010010100100010010100100, 0b00000000000000000000000000000000],
+ expected: 0b10100100010010100100010010100100,
},
{
- input: [V(0b00000000000000000000000000000000), V(0b10100100010010100100010010100100)],
- expected: V(0b10100100010010100100010010100100),
+ input: [0b00000000000000000000000000000000, 0b10100100010010100100010010100100],
+ expected: 0b10100100010010100100010010100100,
},
{
- input: [V(0b01010010001001010010001001010010), V(0b10100100010010100100010010100100)],
- expected: V(0b11110110011011110110011011110110),
+ input: [0b01010010001001010010001001010010, 0b10100100010010100100010010100100],
+ expected: 0b11110110011011110110011011110110,
},
- ];
- // Permute all combinations of a single bit being set for the LHS and RHS
- for (let i = 0; i < 32; i++) {
- const lhs = 1 << i;
- for (let j = 0; j < 32; j++) {
- const rhs = 1 << j;
- cases.push({
- input: [V(lhs), V(rhs)],
- expected: V(lhs | rhs),
+ ],
+ 64: [
+ {
+ input: [
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ ],
+ expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ },
+ {
+ input: [
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ ],
+ expected: 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ },
+ {
+ input: [
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ ],
+ expected: 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ },
+ {
+ input: [
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ ],
+ expected: 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ },
+ {
+ input: [
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ ],
+ expected: 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ },
+ {
+ input: [
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ ],
+ expected: 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ },
+ {
+ input: [
+ 0b0101001000100101001000100101001010100100010010100100010010100100n,
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ ],
+ expected: 0b1111011001101111011001101111011010100100010010100100010010100100n,
+ },
+ ],
+};
+
+/** @returns a set of bitwise-or cases for the specific input type */
+function makeBitwiseOrCases(inputType: string) {
+ const impl = scalarImplForInputType(inputType);
+ const indices =
+ impl.size === 64 ? [...Array(impl.size).keys()].map(BigInt) : [...Array(impl.size).keys()];
+
+ return [
+ ...kBitwiseOrStaticPatterns[impl.size].map(c => {
+ return {
+ input: c.input.map(impl.builder),
+ expected: impl.builder(c.expected),
+ };
+ }),
+ // Permute all combinations of a single bit being set for the LHS and RHS
+ ...indices.flatMap(i => {
+ const lhs = typeof i === 'bigint' ? 1n << i : 1 << i;
+ return indices.map(j => {
+ const rhs = typeof j === 'bigint' ? 1n << j : 1 << j;
+ assert(typeof lhs === typeof rhs);
+ const result = typeof lhs === 'bigint' ? lhs | (rhs as bigint) : lhs | (rhs as number);
+ return { input: [impl.builder(lhs), impl.builder(rhs)], expected: impl.builder(result) };
});
- }
- }
- return cases;
+ }),
+ ];
}
g.test('bitwise_or')
@@ -63,22 +175,25 @@ g.test('bitwise_or')
.desc(
`
e1 | e2: T
-T is i32, u32, vecN<i32>, or vecN<u32>
+T is i32, u32, abstractInt, vecN<i32>, vecN<u32>, or vecN<abstractInt>
Bitwise-or. Component-wise when T is a vector.
`
)
.params(u =>
u
- .combine('type', ['i32', 'u32'] as const)
+ .combine('type', ['i32', 'u32', 'abstract-int'] as const)
.combine('inputSource', allInputSources)
.combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
+ t.skipIf(
+ t.params.type === 'abstract-int' && !onlyConstInputSource.includes(t.params.inputSource)
+ );
const type = scalarType(t.params.type);
const cases = makeBitwiseOrCases(t.params.type);
-
- await run(t, binary('|'), [type, type], type, t.params, cases);
+ const builder = t.params.type === 'abstract-int' ? abstractIntBinary('|') : binary('|');
+ await run(t, builder, [type, type], type, t.params, cases);
});
g.test('bitwise_or_compound')
@@ -104,59 +219,137 @@ Bitwise-or. Component-wise when T is a vector.
await run(t, compoundBinary('|='), [type, type], type, t.params, cases);
});
-function makeBitwiseAndCases(inputType: string) {
- const V = inputType === 'i32' ? i32 : u32;
- const cases = [
- // Static patterns
+/** Manually calculated bitwise-and cases used a check that the CTS test is correct */
+const kBitwiseAndStaticPatterns = {
+ 32: [
{
- input: [V(0b00000000000000000000000000000000), V(0b00000000000000000000000000000000)],
- expected: V(0b00000000000000000000000000000000),
+ input: [0b00000000000000000000000000000000, 0b00000000000000000000000000000000],
+ expected: 0b00000000000000000000000000000000,
},
{
- input: [V(0b11111111111111111111111111111111), V(0b00000000000000000000000000000000)],
- expected: V(0b00000000000000000000000000000000),
+ input: [0b11111111111111111111111111111111, 0b00000000000000000000000000000000],
+ expected: 0b00000000000000000000000000000000,
},
{
- input: [V(0b00000000000000000000000000000000), V(0b11111111111111111111111111111111)],
- expected: V(0b00000000000000000000000000000000),
+ input: [0b00000000000000000000000000000000, 0b11111111111111111111111111111111],
+ expected: 0b00000000000000000000000000000000,
},
{
- input: [V(0b11111111111111111111111111111111), V(0b11111111111111111111111111111111)],
- expected: V(0b11111111111111111111111111111111),
+ input: [0b11111111111111111111111111111111, 0b11111111111111111111111111111111],
+ expected: 0b11111111111111111111111111111111,
},
{
- input: [V(0b10100100010010100100010010100100), V(0b00000000000000000000000000000000)],
- expected: V(0b00000000000000000000000000000000),
+ input: [0b10100100010010100100010010100100, 0b00000000000000000000000000000000],
+ expected: 0b00000000000000000000000000000000,
},
{
- input: [V(0b10100100010010100100010010100100), V(0b11111111111111111111111111111111)],
- expected: V(0b10100100010010100100010010100100),
+ input: [0b10100100010010100100010010100100, 0b11111111111111111111111111111111],
+ expected: 0b10100100010010100100010010100100,
},
{
- input: [V(0b00000000000000000000000000000000), V(0b10100100010010100100010010100100)],
- expected: V(0b00000000000000000000000000000000),
+ input: [0b00000000000000000000000000000000, 0b10100100010010100100010010100100],
+ expected: 0b00000000000000000000000000000000,
},
{
- input: [V(0b11111111111111111111111111111111), V(0b10100100010010100100010010100100)],
- expected: V(0b10100100010010100100010010100100),
+ input: [0b11111111111111111111111111111111, 0b10100100010010100100010010100100],
+ expected: 0b10100100010010100100010010100100,
},
{
- input: [V(0b01010010001001010010001001010010), V(0b01011011101101011011101101011011)],
- expected: V(0b01010010001001010010001001010010),
+ input: [0b01010010001001010010001001010010, 0b01011011101101011011101101011011],
+ expected: 0b01010010001001010010001001010010,
},
- ];
- // Permute all combinations of a single bit being set for the LHS and all but one bit set for the RHS
- for (let i = 0; i < 32; i++) {
- const lhs = 1 << i;
- for (let j = 0; j < 32; j++) {
- const rhs = 0xffffffff ^ (1 << j);
- cases.push({
- input: [V(lhs), V(rhs)],
- expected: V(lhs & rhs),
+ ],
+ 64: [
+ {
+ input: [
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ ],
+ expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ },
+ {
+ input: [
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ ],
+ expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ },
+ {
+ input: [
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ ],
+ expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ },
+ {
+ input: [
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ ],
+ expected: 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ },
+ {
+ input: [
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ ],
+ expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ },
+ {
+ input: [
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ ],
+ expected: 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ },
+ {
+ input: [
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ ],
+ expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ },
+ {
+ input: [
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ ],
+ expected: 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ },
+ {
+ input: [
+ 0b0101001000100101001000100101001001010010001001010010001001010010n,
+ 0b0101101110110101101110110101101101011011101101011011101101011011n,
+ ],
+ expected: 0b0101001000100101001000100101001001010010001001010010001001010010n,
+ },
+ ],
+};
+
+/** @returns a set of bitwise-or cases for the specific input type */
+function makeBitwiseAndCases(inputType: string) {
+ const impl = scalarImplForInputType(inputType);
+ const indices =
+ impl.size === 64 ? [...Array(impl.size).keys()].map(BigInt) : [...Array(impl.size).keys()];
+
+ return [
+ ...kBitwiseAndStaticPatterns[impl.size].map(c => {
+ return {
+ input: c.input.map(impl.builder),
+ expected: impl.builder(c.expected),
+ };
+ }),
+ // Permute all combinations of a single bit being set for the LHS and all but one bit set for the RHS
+ ...indices.flatMap(i => {
+ const lhs = typeof i === 'bigint' ? 1n << i : 1 << i;
+ return indices.map(j => {
+ const rhs = typeof j === 'bigint' ? 0xffffffffffffffffn ^ (1n << j) : 0xffffffff ^ (1 << j);
+ assert(typeof lhs === typeof rhs);
+ const result = typeof lhs === 'bigint' ? lhs & (rhs as bigint) : lhs & (rhs as number);
+ return { input: [impl.builder(lhs), impl.builder(rhs)], expected: impl.builder(result) };
});
- }
- }
- return cases;
+ }),
+ ];
}
g.test('bitwise_and')
@@ -164,21 +357,25 @@ g.test('bitwise_and')
.desc(
`
e1 & e2: T
-T is i32, u32, vecN<i32>, or vecN<u32>
+T is i32, u32, AbstractInt, vecN<i32>, vecN<u32>, or vecN<AbstractInt>
Bitwise-and. Component-wise when T is a vector.
`
)
.params(u =>
u
- .combine('type', ['i32', 'u32'] as const)
+ .combine('type', ['i32', 'u32', 'abstract-int'] as const)
.combine('inputSource', allInputSources)
.combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
+ t.skipIf(
+ t.params.type === 'abstract-int' && !onlyConstInputSource.includes(t.params.inputSource)
+ );
const type = scalarType(t.params.type);
const cases = makeBitwiseAndCases(t.params.type);
- await run(t, binary('&'), [type, type], type, t.params, cases);
+ const builder = t.params.type === 'abstract-int' ? abstractIntBinary('&') : binary('&');
+ await run(t, builder, [type, type], type, t.params, cases);
});
g.test('bitwise_and_compound')
@@ -203,59 +400,137 @@ Bitwise-and. Component-wise when T is a vector.
await run(t, compoundBinary('&='), [type, type], type, t.params, cases);
});
-function makeBitwiseExcluseOrCases(inputType: string) {
- const V = inputType === 'i32' ? i32 : u32;
- const cases = [
- // Static patterns
+/** Manually calculated bitwise-or cases used a check that the CTS test is correct */
+const kBitwiseExclusiveOrStaticPatterns = {
+ 32: [
{
- input: [V(0b00000000000000000000000000000000), V(0b00000000000000000000000000000000)],
- expected: V(0b00000000000000000000000000000000),
+ input: [0b00000000000000000000000000000000, 0b00000000000000000000000000000000],
+ expected: 0b00000000000000000000000000000000,
},
{
- input: [V(0b11111111111111111111111111111111), V(0b00000000000000000000000000000000)],
- expected: V(0b11111111111111111111111111111111),
+ input: [0b11111111111111111111111111111111, 0b00000000000000000000000000000000],
+ expected: 0b11111111111111111111111111111111,
},
{
- input: [V(0b00000000000000000000000000000000), V(0b11111111111111111111111111111111)],
- expected: V(0b11111111111111111111111111111111),
+ input: [0b00000000000000000000000000000000, 0b11111111111111111111111111111111],
+ expected: 0b11111111111111111111111111111111,
},
{
- input: [V(0b11111111111111111111111111111111), V(0b11111111111111111111111111111111)],
- expected: V(0b00000000000000000000000000000000),
+ input: [0b11111111111111111111111111111111, 0b11111111111111111111111111111111],
+ expected: 0b00000000000000000000000000000000,
},
{
- input: [V(0b10100100010010100100010010100100), V(0b00000000000000000000000000000000)],
- expected: V(0b10100100010010100100010010100100),
+ input: [0b10100100010010100100010010100100, 0b00000000000000000000000000000000],
+ expected: 0b10100100010010100100010010100100,
},
{
- input: [V(0b10100100010010100100010010100100), V(0b11111111111111111111111111111111)],
- expected: V(0b01011011101101011011101101011011),
+ input: [0b10100100010010100100010010100100, 0b11111111111111111111111111111111],
+ expected: 0b01011011101101011011101101011011,
},
{
- input: [V(0b00000000000000000000000000000000), V(0b10100100010010100100010010100100)],
- expected: V(0b10100100010010100100010010100100),
+ input: [0b00000000000000000000000000000000, 0b10100100010010100100010010100100],
+ expected: 0b10100100010010100100010010100100,
},
{
- input: [V(0b11111111111111111111111111111111), V(0b10100100010010100100010010100100)],
- expected: V(0b01011011101101011011101101011011),
+ input: [0b11111111111111111111111111111111, 0b10100100010010100100010010100100],
+ expected: 0b01011011101101011011101101011011,
},
{
- input: [V(0b01010010001001010010001001010010), V(0b01011011101101011011101101011011)],
- expected: V(0b00001001100100001001100100001001),
+ input: [0b01010010001001010010001001010010, 0b01011011101101011011101101011011],
+ expected: 0b00001001100100001001100100001001,
},
- ];
- // Permute all combinations of a single bit being set for the LHS and all but one bit set for the RHS
- for (let i = 0; i < 32; i++) {
- const lhs = 1 << i;
- for (let j = 0; j < 32; j++) {
- const rhs = 0xffffffff ^ (1 << j);
- cases.push({
- input: [V(lhs), V(rhs)],
- expected: V(lhs ^ rhs),
+ ],
+ 64: [
+ {
+ input: [
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ ],
+ expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ },
+ {
+ input: [
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ ],
+ expected: 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ },
+ {
+ input: [
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ ],
+ expected: 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ },
+ {
+ input: [
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ ],
+ expected: 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ },
+ {
+ input: [
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ ],
+ expected: 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ },
+ {
+ input: [
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ ],
+ expected: 0b0101101110110101101110110101101101011011101101011011101101011011n,
+ },
+ {
+ input: [
+ 0b0000000000000000000000000000000000000000000000000000000000000000n,
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ ],
+ expected: 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ },
+ {
+ input: [
+ 0b1111111111111111111111111111111111111111111111111111111111111111n,
+ 0b1010010001001010010001001010010010100100010010100100010010100100n,
+ ],
+ expected: 0b0101101110110101101110110101101101011011101101011011101101011011n,
+ },
+ {
+ input: [
+ 0b0101001000100101001000100101001001010010001001010010001001010010n,
+ 0b0101101110110101101110110101101101011011101101011011101101011011n,
+ ],
+ expected: 0b0000100110010000100110010000100100001001100100001001100100001001n,
+ },
+ ],
+};
+
+/** @returns a set of bitwise-xor cases for the specific input type */
+function makeBitwiseExclusiveOrCases(inputType: string) {
+ const impl = scalarImplForInputType(inputType);
+ const indices =
+ impl.size === 64 ? [...Array(impl.size).keys()].map(BigInt) : [...Array(impl.size).keys()];
+
+ return [
+ ...kBitwiseExclusiveOrStaticPatterns[impl.size].map(c => {
+ return {
+ input: c.input.map(impl.builder),
+ expected: impl.builder(c.expected),
+ };
+ }),
+ // Permute all combinations of a single bit being set for the LHS and all but one bit set for the RHS
+ ...indices.flatMap(i => {
+ const lhs = typeof i === 'bigint' ? 1n << i : 1 << i;
+ return indices.map(j => {
+ const rhs = typeof j === 'bigint' ? 0xffffffffffffffffn ^ (1n << j) : 0xffffffff ^ (1 << j);
+ assert(typeof lhs === typeof rhs);
+ const result = typeof lhs === 'bigint' ? lhs ^ (rhs as bigint) : lhs ^ (rhs as number);
+ return { input: [impl.builder(lhs), impl.builder(rhs)], expected: impl.builder(result) };
});
- }
- }
- return cases;
+ }),
+ ];
}
g.test('bitwise_exclusive_or')
@@ -263,21 +538,25 @@ g.test('bitwise_exclusive_or')
.desc(
`
e1 ^ e2: T
-T is i32, u32, vecN<i32>, or vecN<u32>
+T is i32, u32, abstractInt, vecN<i32>, vecN<u32>, or vecN<abstractInt>
Bitwise-exclusive-or. Component-wise when T is a vector.
`
)
.params(u =>
u
- .combine('type', ['i32', 'u32'] as const)
+ .combine('type', ['i32', 'u32', 'abstract-int'] as const)
.combine('inputSource', allInputSources)
.combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
+ t.skipIf(
+ t.params.type === 'abstract-int' && !onlyConstInputSource.includes(t.params.inputSource)
+ );
const type = scalarType(t.params.type);
- const cases = makeBitwiseExcluseOrCases(t.params.type);
- await run(t, binary('^'), [type, type], type, t.params, cases);
+ const cases = makeBitwiseExclusiveOrCases(t.params.type);
+ const builder = t.params.type === 'abstract-int' ? abstractIntBinary('^') : binary('^');
+ await run(t, builder, [type, type], type, t.params, cases);
});
g.test('bitwise_exclusive_or_compound')
@@ -298,6 +577,6 @@ Bitwise-exclusive-or. Component-wise when T is a vector.
)
.fn(async t => {
const type = scalarType(t.params.type);
- const cases = makeBitwiseExcluseOrCases(t.params.type);
+ const cases = makeBitwiseExclusiveOrCases(t.params.type);
await run(t, compoundBinary('^='), [type, type], type, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise_shift.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise_shift.spec.ts
index 5457b7ceab..e2ed29d3c2 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise_shift.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bitwise_shift.spec.ts
@@ -4,8 +4,9 @@ Execution Tests for the bitwise shift binary expression operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { i32, scalarType, ScalarType, TypeU32, u32 } from '../../../../util/conversion.js';
-import { allInputSources, CaseList, run } from '../expression.js';
+import { i32, scalarType, ScalarType, Type, u32 } from '../../../../util/conversion.js';
+import { Case } from '../case.js';
+import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
@@ -65,9 +66,9 @@ function is_valid_const_shift_right(e1: number, e1Type: string, e2: number) {
// Returns all cases of shifting e1 left by [0,63]. If `is_const` is true, cases that are
// invalid for const eval are not returned.
-function generate_shift_left_cases(e1: number, e1Type: string, is_const: boolean): CaseList {
+function generate_shift_left_cases(e1: number, e1Type: string, is_const: boolean): Case[] {
const V = e1Type === 'i32' ? i32 : u32;
- const cases: CaseList = [];
+ const cases: Case[] = [];
for (let shift = 0; shift < 64; ++shift) {
const e2 = shift;
if (is_const && !is_valid_const_shift_left(e1, e1Type, e2)) {
@@ -81,9 +82,9 @@ function generate_shift_left_cases(e1: number, e1Type: string, is_const: boolean
// Returns all cases of shifting e1 right by [0,63]. If `is_const` is true, cases that are
// invalid for const eval are not returned.
-function generate_shift_right_cases(e1: number, e1Type: string, is_const: boolean): CaseList {
+function generate_shift_right_cases(e1: number, e1Type: string, is_const: boolean): Case[] {
const V = e1Type === 'i32' ? i32 : u32;
- const cases: CaseList = [];
+ const cases: Case[] = [];
for (let shift = 0; shift < 64; ++shift) {
const e2 = shift;
if (is_const && !is_valid_const_shift_right(e1, e1Type, e2)) {
@@ -107,7 +108,7 @@ function makeShiftLeftConcreteCases(inputType: string, inputSource: string, type
const V = inputType === 'i32' ? i32 : u32;
const is_const = inputSource === 'const';
- const cases: CaseList = [
+ const cases: Case[] = [
{
input: /* */ [V(0b00000000000000000000000000000001), u32(1)],
expected: /**/ V(0b00000000000000000000000000000010),
@@ -193,7 +194,7 @@ Shift left (shifted value is concrete)
.fn(async t => {
const type = scalarType(t.params.type);
const cases = makeShiftLeftConcreteCases(t.params.type, t.params.inputSource, type);
- await run(t, binary('<<'), [type, TypeU32], type, t.params, cases);
+ await run(t, binary('<<'), [type, Type.u32], type, t.params, cases);
});
g.test('shift_left_concrete_compound')
@@ -214,14 +215,14 @@ Shift left (shifted value is concrete)
.fn(async t => {
const type = scalarType(t.params.type);
const cases = makeShiftLeftConcreteCases(t.params.type, t.params.inputSource, type);
- await run(t, compoundBinary('<<='), [type, TypeU32], type, t.params, cases);
+ await run(t, compoundBinary('<<='), [type, Type.u32], type, t.params, cases);
});
function makeShiftRightConcreteCases(inputType: string, inputSource: string, type: ScalarType) {
const V = inputType === 'i32' ? i32 : u32;
const is_const = inputSource === 'const';
- const cases: CaseList = [
+ const cases: Case[] = [
{
input: /* */ [V(0b00000000000000000000000000000001), u32(1)],
expected: /**/ V(0b00000000000000000000000000000000),
@@ -318,7 +319,7 @@ Shift right (shifted value is concrete)
.fn(async t => {
const type = scalarType(t.params.type);
const cases = makeShiftRightConcreteCases(t.params.type, t.params.inputSource, type);
- await run(t, binary('>>'), [type, TypeU32], type, t.params, cases);
+ await run(t, binary('>>'), [type, Type.u32], type, t.params, cases);
});
g.test('shift_right_concrete_compound')
@@ -339,5 +340,5 @@ Shift right (shifted value is concrete)
.fn(async t => {
const type = scalarType(t.params.type);
const cases = makeShiftRightConcreteCases(t.params.type, t.params.inputSource, type);
- await run(t, compoundBinary('>>='), [type, TypeU32], type, t.params, cases);
+ await run(t, compoundBinary('>>='), [type, Type.u32], type, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bool_logical.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bool_logical.spec.ts
index e3aa448fe3..0e76f50824 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bool_logical.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/bool_logical.spec.ts
@@ -4,7 +4,7 @@ Execution Tests for the boolean binary logical expression operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { bool, TypeBool } from '../../../../util/conversion.js';
+import { bool, Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
@@ -33,7 +33,7 @@ Logical "and". Component-wise when T is a vector. Evaluates both e1 and e2.
{ input: [bool(true), bool(true)], expected: bool(true) },
];
- await run(t, binary('&'), [TypeBool, TypeBool], TypeBool, t.params, cases);
+ await run(t, binary('&'), [Type.bool, Type.bool], Type.bool, t.params, cases);
});
g.test('and_compound')
@@ -55,7 +55,7 @@ Logical "and". Component-wise when T is a vector. Evaluates both e1 and e2.
{ input: [bool(true), bool(true)], expected: bool(true) },
];
- await run(t, compoundBinary('&='), [TypeBool, TypeBool], TypeBool, t.params, cases);
+ await run(t, compoundBinary('&='), [Type.bool, Type.bool], Type.bool, t.params, cases);
});
g.test('and_short_circuit')
@@ -75,7 +75,7 @@ short_circuiting "and". Yields true if both e1 and e2 are true; evaluates e2 onl
{ input: [bool(true), bool(true)], expected: bool(true) },
];
- await run(t, binary('&&'), [TypeBool, TypeBool], TypeBool, t.params, cases);
+ await run(t, binary('&&'), [Type.bool, Type.bool], Type.bool, t.params, cases);
});
g.test('or')
@@ -97,7 +97,7 @@ Logical "or". Component-wise when T is a vector. Evaluates both e1 and e2.
{ input: [bool(true), bool(true)], expected: bool(true) },
];
- await run(t, binary('|'), [TypeBool, TypeBool], TypeBool, t.params, cases);
+ await run(t, binary('|'), [Type.bool, Type.bool], Type.bool, t.params, cases);
});
g.test('or_compound')
@@ -119,7 +119,7 @@ Logical "or". Component-wise when T is a vector. Evaluates both e1 and e2.
{ input: [bool(true), bool(true)], expected: bool(true) },
];
- await run(t, compoundBinary('|='), [TypeBool, TypeBool], TypeBool, t.params, cases);
+ await run(t, compoundBinary('|='), [Type.bool, Type.bool], Type.bool, t.params, cases);
});
g.test('or_short_circuit')
@@ -139,7 +139,7 @@ short_circuiting "and". Yields true if both e1 and e2 are true; evaluates e2 onl
{ input: [bool(true), bool(true)], expected: bool(true) },
];
- await run(t, binary('||'), [TypeBool, TypeBool], TypeBool, t.params, cases);
+ await run(t, binary('||'), [Type.bool, Type.bool], Type.bool, t.params, cases);
});
g.test('equals')
@@ -161,7 +161,7 @@ Equality. Component-wise when T is a vector.
{ input: [bool(true), bool(true)], expected: bool(true) },
];
- await run(t, binary('=='), [TypeBool, TypeBool], TypeBool, t.params, cases);
+ await run(t, binary('=='), [Type.bool, Type.bool], Type.bool, t.params, cases);
});
g.test('not_equals')
@@ -183,5 +183,5 @@ Equality. Component-wise when T is a vector.
{ input: [bool(true), bool(true)], expected: bool(false) },
];
- await run(t, binary('!='), [TypeBool, TypeBool], TypeBool, t.params, cases);
+ await run(t, binary('!='), [Type.bool, Type.bool], Type.bool, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_addition.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_addition.cache.ts
new file mode 100644
index 0000000000..f179d48a13
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_addition.cache.ts
@@ -0,0 +1,60 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF16Range, sparseVectorF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const additionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.additionInterval(e, s)));
+};
+
+const additionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.additionInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ sparseScalarF16Range(),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.additionInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorScalarToVectorCases(
+ sparseVectorF16Range(dim),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ additionVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarVectorToVectorCases(
+ sparseScalarF16Range(),
+ sparseVectorF16Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ additionScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_addition', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_addition.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_addition.spec.ts
index 8948f90499..d9aa44bba0 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_addition.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_addition.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f16 addition expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF16Range, sparseVectorF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const additionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.additionInterval(e, s)));
-};
-
-const additionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.additionInterval(s, e)));
-};
+import { d } from './f16_addition.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.additionInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorScalarToVectorCases(
- sparseVectorF16Range(dim),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- additionVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarVectorToVectorCases(
- sparseF16Range(),
- sparseVectorF16Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- additionScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_addition', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -87,7 +28,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('+'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, binary('+'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector')
@@ -106,7 +47,7 @@ Accuracy: Correctly rounded
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);
+ await run(t, binary('+'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('scalar_compound')
@@ -127,7 +68,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('+='), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, compoundBinary('+='), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector_scalar')
@@ -150,8 +91,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('+'),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -177,8 +118,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('+='),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -204,8 +145,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('+'),
- [TypeF16, TypeVec(dim, TypeF16)],
- TypeVec(dim, TypeF16),
+ [Type.f16, Type.vec(dim, Type.f16)],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_comparison.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_comparison.cache.ts
new file mode 100644
index 0000000000..c0c0d4f8a4
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_comparison.cache.ts
@@ -0,0 +1,144 @@
+import { anyOf } from '../../../../util/compare.js';
+import { bool, f16, ScalarValue } from '../../../../util/conversion.js';
+import { flushSubnormalNumberF16, vectorF16Range } from '../../../../util/math.js';
+import { Case } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+/**
+ * @returns a test case for the provided left hand & right hand values and truth function.
+ * Handles quantization and subnormals.
+ */
+function makeCase(
+ lhs: number,
+ rhs: number,
+ truthFunc: (lhs: ScalarValue, rhs: ScalarValue) => boolean
+): Case {
+ // Subnormal float values may be flushed at any time.
+ // https://www.w3.org/TR/WGSL/#floating-point-evaluation
+ const f16_lhs = f16(lhs);
+ const f16_rhs = f16(rhs);
+ const lhs_options = new Set([f16_lhs, f16(flushSubnormalNumberF16(lhs))]);
+ const rhs_options = new Set([f16_rhs, f16(flushSubnormalNumberF16(rhs))]);
+ const expected: Array<ScalarValue> = [];
+ lhs_options.forEach(l => {
+ rhs_options.forEach(r => {
+ const result = bool(truthFunc(l, r));
+ if (!expected.includes(result)) {
+ expected.push(result);
+ }
+ });
+ });
+
+ return { input: [f16_lhs, f16_rhs], expected: anyOf(...expected) };
+}
+
+export const d = makeCaseCache('binary/f16_logical', {
+ equals_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) === (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ equals_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) === (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ not_equals_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) !== (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ not_equals_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) !== (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_than_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) < (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_than_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) < (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_equals_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) <= (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_equals_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) <= (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_than_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) > (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_than_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) > (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_equals_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) >= (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_equals_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) >= (rhs.value as number);
+ };
+
+ return vectorF16Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_comparison.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_comparison.spec.ts
index ae7e1675c5..b978cd3c99 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_comparison.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_comparison.spec.ts
@@ -4,155 +4,14 @@ Execution Tests for the f16 comparison operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { anyOf } from '../../../../util/compare.js';
-import { bool, f16, Scalar, TypeBool, TypeF16 } from '../../../../util/conversion.js';
-import { flushSubnormalNumberF16, vectorF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, Case, run } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { allInputSources, run } from '../expression.js';
import { binary } from './binary.js';
+import { d } from './f16_comparison.cache.js';
export const g = makeTestGroup(GPUTest);
-/**
- * @returns a test case for the provided left hand & right hand values and truth function.
- * Handles quantization and subnormals.
- */
-function makeCase(
- lhs: number,
- rhs: number,
- truthFunc: (lhs: Scalar, rhs: Scalar) => boolean
-): Case {
- // Subnormal float values may be flushed at any time.
- // https://www.w3.org/TR/WGSL/#floating-point-evaluation
- const f16_lhs = f16(lhs);
- const f16_rhs = f16(rhs);
- const lhs_options = new Set([f16_lhs, f16(flushSubnormalNumberF16(lhs))]);
- const rhs_options = new Set([f16_rhs, f16(flushSubnormalNumberF16(rhs))]);
- const expected: Array<Scalar> = [];
- lhs_options.forEach(l => {
- rhs_options.forEach(r => {
- const result = bool(truthFunc(l, r));
- if (!expected.includes(result)) {
- expected.push(result);
- }
- });
- });
-
- return { input: [f16_lhs, f16_rhs], expected: anyOf(...expected) };
-}
-
-export const d = makeCaseCache('binary/f16_logical', {
- equals_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) === (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- equals_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) === (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- not_equals_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) !== (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- not_equals_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) !== (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_than_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) < (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_than_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) < (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_equals_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) <= (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_equals_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) <= (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_than_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) > (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_than_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) > (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_equals_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) >= (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_equals_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) >= (rhs.value as number);
- };
-
- return vectorF16Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
-});
-
g.test('equals')
.specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
.desc(
@@ -171,7 +30,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'equals_const' : 'equals_non_const'
);
- await run(t, binary('=='), [TypeF16, TypeF16], TypeBool, t.params, cases);
+ await run(t, binary('=='), [Type.f16, Type.f16], Type.bool, t.params, cases);
});
g.test('not_equals')
@@ -192,7 +51,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'not_equals_const' : 'not_equals_non_const'
);
- await run(t, binary('!='), [TypeF16, TypeF16], TypeBool, t.params, cases);
+ await run(t, binary('!='), [Type.f16, Type.f16], Type.bool, t.params, cases);
});
g.test('less_than')
@@ -213,7 +72,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'less_than_const' : 'less_than_non_const'
);
- await run(t, binary('<'), [TypeF16, TypeF16], TypeBool, t.params, cases);
+ await run(t, binary('<'), [Type.f16, Type.f16], Type.bool, t.params, cases);
});
g.test('less_equals')
@@ -234,7 +93,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'less_equals_const' : 'less_equals_non_const'
);
- await run(t, binary('<='), [TypeF16, TypeF16], TypeBool, t.params, cases);
+ await run(t, binary('<='), [Type.f16, Type.f16], Type.bool, t.params, cases);
});
g.test('greater_than')
@@ -255,7 +114,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'greater_than_const' : 'greater_than_non_const'
);
- await run(t, binary('>'), [TypeF16, TypeF16], TypeBool, t.params, cases);
+ await run(t, binary('>'), [Type.f16, Type.f16], Type.bool, t.params, cases);
});
g.test('greater_equals')
@@ -276,5 +135,5 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'greater_equals_const' : 'greater_equals_non_const'
);
- await run(t, binary('>='), [TypeF16, TypeF16], TypeBool, t.params, cases);
+ await run(t, binary('>='), [Type.f16, Type.f16], Type.bool, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_division.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_division.cache.ts
new file mode 100644
index 0000000000..95590ca467
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_division.cache.ts
@@ -0,0 +1,60 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF16Range, sparseVectorF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const divisionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.divisionInterval(e, s)));
+};
+
+const divisionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.divisionInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ sparseScalarF16Range(),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.divisionInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorScalarToVectorCases(
+ sparseVectorF16Range(dim),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ divisionVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarVectorToVectorCases(
+ sparseScalarF16Range(),
+ sparseVectorF16Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ divisionScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_division', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_division.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_division.spec.ts
index c3b8fc04db..8a155024db 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_division.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_division.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f16 division expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF16Range, sparseVectorF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const divisionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.divisionInterval(e, s)));
-};
-
-const divisionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.divisionInterval(s, e)));
-};
+import { d } from './f16_division.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.divisionInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorScalarToVectorCases(
- sparseVectorF16Range(dim),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- divisionVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarVectorToVectorCases(
- sparseF16Range(),
- sparseVectorF16Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- divisionScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_division', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -87,7 +28,7 @@ Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('/'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, binary('/'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector')
@@ -106,7 +47,7 @@ Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
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);
+ await run(t, binary('/'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('scalar_compound')
@@ -127,7 +68,7 @@ Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('/='), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, compoundBinary('/='), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector_scalar')
@@ -150,8 +91,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('/'),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -177,8 +118,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('/='),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -204,8 +145,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('/'),
- [TypeF16, TypeVec(dim, TypeF16)],
- TypeVec(dim, TypeF16),
+ [Type.f16, Type.vec(dim, Type.f16)],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_addition.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_addition.cache.ts
new file mode 100644
index 0000000000..a670b08b07
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_addition.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_[non_]const
+const mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateMatrixPairToMatrixCases(
+ sparseMatrixF16Range(cols, rows),
+ sparseMatrixF16Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.additionMatrixMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_matrix_addition', mat_cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_addition.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_addition.spec.ts
index fe64f41503..7c34b0cadd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_addition.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_addition.spec.ts
@@ -4,36 +4,14 @@ Execution Tests for matrix f16 addition expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeMat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseMatrixF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f16_matrix_addition.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR_[non_]const
-const mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateMatrixPairToMatrixCases(
- sparseMatrixF16Range(cols, rows),
- sparseMatrixF16Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.additionMatrixMatrixInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_matrix_addition', mat_cases);
-
g.test('matrix')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -60,8 +38,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('+'),
- [TypeMat(cols, rows, TypeF16), TypeMat(cols, rows, TypeF16)],
- TypeMat(cols, rows, TypeF16),
+ [Type.mat(cols, rows, Type.f16), Type.mat(cols, rows, Type.f16)],
+ Type.mat(cols, rows, Type.f16),
t.params,
cases
);
@@ -93,8 +71,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('+='),
- [TypeMat(cols, rows, TypeF16), TypeMat(cols, rows, TypeF16)],
- TypeMat(cols, rows, TypeF16),
+ [Type.mat(cols, rows, Type.f16), Type.mat(cols, rows, Type.f16)],
+ Type.mat(cols, rows, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.cache.ts
new file mode 100644
index 0000000000..a31813abb3
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.cache.ts
@@ -0,0 +1,25 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matKxR_matCxK_[non_]const
+const mat_mat_cases = ([2, 3, 4] as const)
+ .flatMap(k =>
+ ([2, 3, 4] as const).flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${k}x${rows}_mat${cols}x${k}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateMatrixPairToMatrixCases(
+ sparseMatrixF16Range(k, rows),
+ sparseMatrixF16Range(cols, k),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.multiplicationMatrixMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_matrix_matrix_multiplication', mat_mat_cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.spec.ts
index 0c8b3e8c51..80ca78f7f5 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_matrix_multiplication.spec.ts
@@ -4,38 +4,14 @@ Execution Tests for matrix-matrix f16 multiplication expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeMat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseMatrixF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f16_matrix_matrix_multiplication.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matKxR_matCxK_[non_]const
-const mat_mat_cases = ([2, 3, 4] as const)
- .flatMap(k =>
- ([2, 3, 4] as const).flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${k}x${rows}_mat${cols}x${k}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateMatrixPairToMatrixCases(
- sparseMatrixF16Range(k, rows),
- sparseMatrixF16Range(cols, k),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.multiplicationMatrixMatrixInterval
- );
- },
- }))
- )
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_matrix_matrix_multiplication', mat_mat_cases);
-
g.test('matrix_matrix')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -68,8 +44,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeMat(x_cols, x_rows, TypeF16), TypeMat(y_cols, y_rows, TypeF16)],
- TypeMat(y_cols, x_rows, TypeF16),
+ [Type.mat(x_cols, x_rows, Type.f16), Type.mat(y_cols, y_rows, Type.f16)],
+ Type.mat(y_cols, x_rows, Type.f16),
t.params,
cases
);
@@ -106,8 +82,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('*='),
- [TypeMat(x_cols, x_rows, TypeF16), TypeMat(y_cols, y_rows, TypeF16)],
- TypeMat(y_cols, x_rows, TypeF16),
+ [Type.mat(x_cols, x_rows, Type.f16), Type.mat(y_cols, y_rows, Type.f16)],
+ Type.mat(y_cols, x_rows, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.cache.ts
new file mode 100644
index 0000000000..f902a1c8bc
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.cache.ts
@@ -0,0 +1,44 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF16Range, sparseScalarF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_scalar_[non_]const
+const mat_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${cols}x${rows}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateMatrixScalarToMatrixCases(
+ sparseMatrixF16Range(cols, rows),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.multiplicationMatrixScalarInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: scalar_matCxR_[non_]const
+const scalar_mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarMatrixToMatrixCases(
+ sparseScalarF16Range(),
+ sparseMatrixF16Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.multiplicationScalarMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_matrix_scalar_multiplication', {
+ ...mat_scalar_cases,
+ ...scalar_mat_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.spec.ts
index 29d4700ee6..aa7087738a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_scalar_multiplication.spec.ts
@@ -4,57 +4,14 @@ Execution Tests for matrix-scalar and scalar-matrix f16 multiplication expressio
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeMat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseF16Range, sparseMatrixF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f16_matrix_scalar_multiplication.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR_scalar_[non_]const
-const mat_scalar_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${cols}x${rows}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateMatrixScalarToMatrixCases(
- sparseMatrixF16Range(cols, rows),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.multiplicationMatrixScalarInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: scalar_matCxR_[non_]const
-const scalar_mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarMatrixToMatrixCases(
- sparseF16Range(),
- sparseMatrixF16Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.multiplicationScalarMatrixInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_matrix_scalar_multiplication', {
- ...mat_scalar_cases,
- ...scalar_mat_cases,
-});
-
g.test('matrix_scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -83,8 +40,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeMat(cols, rows, TypeF16), TypeF16],
- TypeMat(cols, rows, TypeF16),
+ [Type.mat(cols, rows, Type.f16), Type.f16],
+ Type.mat(cols, rows, Type.f16),
t.params,
cases
);
@@ -118,8 +75,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('*='),
- [TypeMat(cols, rows, TypeF16), TypeF16],
- TypeMat(cols, rows, TypeF16),
+ [Type.mat(cols, rows, Type.f16), Type.f16],
+ Type.mat(cols, rows, Type.f16),
t.params,
cases
);
@@ -153,8 +110,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeF16, TypeMat(cols, rows, TypeF16)],
- TypeMat(cols, rows, TypeF16),
+ [Type.f16, Type.mat(cols, rows, Type.f16)],
+ Type.mat(cols, rows, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.cache.ts
new file mode 100644
index 0000000000..a6edcf7fe5
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_[non_]const
+const mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateMatrixPairToMatrixCases(
+ sparseMatrixF16Range(cols, rows),
+ sparseMatrixF16Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.subtractionMatrixMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_matrix_subtraction', mat_cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.spec.ts
index 5b5f6ba04e..e8e13d902a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_subtraction.spec.ts
@@ -4,36 +4,14 @@ Execution Tests for matrix f16 subtraction expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeMat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseMatrixF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f16_matrix_subtraction.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR_[non_]const
-const mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateMatrixPairToMatrixCases(
- sparseMatrixF16Range(cols, rows),
- sparseMatrixF16Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.subtractionMatrixMatrixInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_matrix_subtraction', mat_cases);
-
g.test('matrix')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -60,8 +38,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('-'),
- [TypeMat(cols, rows, TypeF16), TypeMat(cols, rows, TypeF16)],
- TypeMat(cols, rows, TypeF16),
+ [Type.mat(cols, rows, Type.f16), Type.mat(cols, rows, Type.f16)],
+ Type.mat(cols, rows, Type.f16),
t.params,
cases
);
@@ -93,8 +71,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('-='),
- [TypeMat(cols, rows, TypeF16), TypeMat(cols, rows, TypeF16)],
- TypeMat(cols, rows, TypeF16),
+ [Type.mat(cols, rows, Type.f16), Type.mat(cols, rows, Type.f16)],
+ Type.mat(cols, rows, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.cache.ts
new file mode 100644
index 0000000000..7b822386fe
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.cache.ts
@@ -0,0 +1,44 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF16Range, sparseVectorF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_vecC_[non_]const
+const mat_vec_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${cols}x${rows}_vec${cols}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateMatrixVectorToVectorCases(
+ sparseMatrixF16Range(cols, rows),
+ sparseVectorF16Range(cols),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.multiplicationMatrixVectorInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: vecR_matCxR_[non_]const
+const vec_mat_cases = ([2, 3, 4] as const)
+ .flatMap(rows =>
+ ([2, 3, 4] as const).flatMap(cols =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${rows}_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorMatrixToVectorCases(
+ sparseVectorF16Range(rows),
+ sparseMatrixF16Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.multiplicationVectorMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_matrix_vector_multiplication', {
+ ...mat_vec_cases,
+ ...vec_mat_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.spec.ts
index 3e916c7fd4..557a7cead8 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_matrix_vector_multiplication.spec.ts
@@ -4,57 +4,14 @@ Execution Tests for matrix-vector and vector-matrix f16 multiplication expressio
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeMat, TypeVec } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseMatrixF16Range, sparseVectorF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f16_matrix_vector_multiplication.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR_vecC_[non_]const
-const mat_vec_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${cols}x${rows}_vec${cols}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateMatrixVectorToVectorCases(
- sparseMatrixF16Range(cols, rows),
- sparseVectorF16Range(cols),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.multiplicationMatrixVectorInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: vecR_matCxR_[non_]const
-const vec_mat_cases = ([2, 3, 4] as const)
- .flatMap(rows =>
- ([2, 3, 4] as const).flatMap(cols =>
- ([true, false] as const).map(nonConst => ({
- [`vec${rows}_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorMatrixToVectorCases(
- sparseVectorF16Range(rows),
- sparseMatrixF16Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.multiplicationVectorMatrixInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_matrix_vector_multiplication', {
- ...mat_vec_cases,
- ...vec_mat_cases,
-});
-
g.test('matrix_vector')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -83,8 +40,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeMat(cols, rows, TypeF16), TypeVec(cols, TypeF16)],
- TypeVec(rows, TypeF16),
+ [Type.mat(cols, rows, Type.f16), Type.vec(cols, Type.f16)],
+ Type.vec(rows, Type.f16),
t.params,
cases
);
@@ -118,8 +75,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeVec(rows, TypeF16), TypeMat(cols, rows, TypeF16)],
- TypeVec(cols, TypeF16),
+ [Type.vec(rows, Type.f16), Type.mat(cols, rows, Type.f16)],
+ Type.vec(cols, Type.f16),
t.params,
cases
);
@@ -148,8 +105,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('*='),
- [TypeVec(rows, TypeF16), TypeMat(cols, rows, TypeF16)],
- TypeVec(cols, TypeF16),
+ [Type.vec(rows, Type.f16), Type.mat(cols, rows, Type.f16)],
+ Type.vec(cols, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_multiplication.cache.ts
new file mode 100644
index 0000000000..a94f55ccf0
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_multiplication.cache.ts
@@ -0,0 +1,60 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF16Range, sparseVectorF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const multiplicationVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.multiplicationInterval(e, s)));
+};
+
+const multiplicationScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.multiplicationInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ sparseScalarF16Range(),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.multiplicationInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorScalarToVectorCases(
+ sparseVectorF16Range(dim),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ multiplicationVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarVectorToVectorCases(
+ sparseScalarF16Range(),
+ sparseVectorF16Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ multiplicationScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_multiplication', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_multiplication.spec.ts
index 10041fbc17..81339d9266 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_multiplication.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_multiplication.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f16 multiplication expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF16Range, sparseVectorF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const multiplicationVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.multiplicationInterval(e, s)));
-};
-
-const multiplicationScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.multiplicationInterval(s, e)));
-};
+import { d } from './f16_multiplication.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.multiplicationInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorScalarToVectorCases(
- sparseVectorF16Range(dim),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- multiplicationVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarVectorToVectorCases(
- sparseF16Range(),
- sparseVectorF16Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- multiplicationScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_multiplication', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -87,7 +28,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('*'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, binary('*'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector')
@@ -106,7 +47,7 @@ Accuracy: Correctly rounded
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);
+ await run(t, binary('*'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('scalar_compound')
@@ -127,7 +68,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('*='), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, compoundBinary('*='), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector_scalar')
@@ -150,8 +91,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -177,8 +118,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('*='),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -204,8 +145,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeF16, TypeVec(dim, TypeF16)],
- TypeVec(dim, TypeF16),
+ [Type.f16, Type.vec(dim, Type.f16)],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_remainder.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_remainder.cache.ts
new file mode 100644
index 0000000000..2c1cdc0c38
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_remainder.cache.ts
@@ -0,0 +1,60 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF16Range, sparseVectorF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const remainderVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.remainderInterval(e, s)));
+};
+
+const remainderScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.remainderInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ sparseScalarF16Range(),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.remainderInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorScalarToVectorCases(
+ sparseVectorF16Range(dim),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ remainderVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarVectorToVectorCases(
+ sparseScalarF16Range(),
+ sparseVectorF16Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ remainderScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_remainder', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_remainder.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_remainder.spec.ts
index 801b84904b..0fe1cc53c6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_remainder.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_remainder.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f16 remainder expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF16Range, sparseVectorF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const remainderVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.remainderInterval(e, s)));
-};
-
-const remainderScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.remainderInterval(s, e)));
-};
+import { d } from './f16_remainder.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.remainderInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorScalarToVectorCases(
- sparseVectorF16Range(dim),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- remainderVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarVectorToVectorCases(
- sparseF16Range(),
- sparseVectorF16Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- remainderScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_remainder', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -87,7 +28,7 @@ Accuracy: Derived from x - y * trunc(x/y)
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('%'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, binary('%'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector')
@@ -106,7 +47,7 @@ Accuracy: Derived from x - y * trunc(x/y)
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('%'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, binary('%'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('scalar_compound')
@@ -127,7 +68,7 @@ Accuracy: Derived from x - y * trunc(x/y)
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('%='), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, compoundBinary('%='), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector_scalar')
@@ -150,8 +91,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('%'),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -177,8 +118,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('%='),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -204,8 +145,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('%'),
- [TypeF16, TypeVec(dim, TypeF16)],
- TypeVec(dim, TypeF16),
+ [Type.f16, Type.vec(dim, Type.f16)],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_subtraction.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_subtraction.cache.ts
new file mode 100644
index 0000000000..a68b58449b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_subtraction.cache.ts
@@ -0,0 +1,60 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF16Range, sparseVectorF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const subtractionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.subtractionInterval(e, s)));
+};
+
+const subtractionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f16.toVector(v.map(e => FP.f16.subtractionInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarPairToIntervalCases(
+ sparseScalarF16Range(),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.subtractionInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateVectorScalarToVectorCases(
+ sparseVectorF16Range(dim),
+ sparseScalarF16Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ subtractionVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateScalarVectorToVectorCases(
+ sparseScalarF16Range(),
+ sparseVectorF16Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ subtractionScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f16_subtraction', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_subtraction.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_subtraction.spec.ts
index a64d556837..6b29aad6ad 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_subtraction.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f16_subtraction.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f16 subtraction expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF16, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF16Range, sparseVectorF16Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const subtractionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.subtractionInterval(e, s)));
-};
-
-const subtractionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f16.toVector(v.map(e => FP.f16.subtractionInterval(s, e)));
-};
+import { d } from './f16_subtraction.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.subtractionInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorScalarToVectorCases(
- sparseVectorF16Range(dim),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- subtractionVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateScalarVectorToVectorCases(
- sparseF16Range(),
- sparseVectorF16Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- subtractionScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f16_subtraction', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -87,7 +28,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('-'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, binary('-'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector')
@@ -106,7 +47,7 @@ Accuracy: Correctly rounded
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);
+ await run(t, binary('-'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('scalar_compound')
@@ -127,7 +68,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('-='), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, compoundBinary('-='), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('vector_scalar')
@@ -150,8 +91,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('-'),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -177,8 +118,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('-='),
- [TypeVec(dim, TypeF16), TypeF16],
- TypeVec(dim, TypeF16),
+ [Type.vec(dim, Type.f16), Type.f16],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
@@ -204,8 +145,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('-'),
- [TypeF16, TypeVec(dim, TypeF16)],
- TypeVec(dim, TypeF16),
+ [Type.f16, Type.vec(dim, Type.f16)],
+ Type.vec(dim, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_addition.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_addition.cache.ts
new file mode 100644
index 0000000000..9353671fb0
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_addition.cache.ts
@@ -0,0 +1,60 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF32Range, sparseVectorF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const additionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.additionInterval(e, s)));
+};
+
+const additionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.additionInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ sparseScalarF32Range(),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.additionInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateVectorScalarToVectorCases(
+ sparseVectorF32Range(dim),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ additionVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarVectorToVectorCases(
+ sparseScalarF32Range(),
+ sparseVectorF32Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ additionScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_addition', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts
index 65739f67ca..9a502ae677 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_addition.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f32 addition expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF32Range, sparseVectorF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const additionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.additionInterval(e, s)));
-};
-
-const additionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.additionInterval(s, e)));
-};
+import { d } from './f32_addition.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.additionInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorScalarToVectorCases(
- sparseVectorF32Range(dim),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- additionVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarVectorToVectorCases(
- sparseF32Range(),
- sparseVectorF32Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- additionScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_addition', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -84,7 +25,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('+'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, binary('+'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector')
@@ -100,7 +41,7 @@ Accuracy: Correctly rounded
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);
+ await run(t, binary('+'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('scalar_compound')
@@ -118,7 +59,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('+='), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, compoundBinary('+='), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector_scalar')
@@ -138,8 +79,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('+'),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -162,8 +103,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('+='),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -186,8 +127,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('+'),
- [TypeF32, TypeVec(dim, TypeF32)],
- TypeVec(dim, TypeF32),
+ [Type.f32, Type.vec(dim, Type.f32)],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_comparison.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_comparison.cache.ts
new file mode 100644
index 0000000000..28fb22e820
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_comparison.cache.ts
@@ -0,0 +1,144 @@
+import { anyOf } from '../../../../util/compare.js';
+import { bool, f32, ScalarValue } from '../../../../util/conversion.js';
+import { flushSubnormalNumberF32, vectorF32Range } from '../../../../util/math.js';
+import { Case } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+/**
+ * @returns a test case for the provided left hand & right hand values and truth function.
+ * Handles quantization and subnormals.
+ */
+function makeCase(
+ lhs: number,
+ rhs: number,
+ truthFunc: (lhs: ScalarValue, rhs: ScalarValue) => boolean
+): Case {
+ // Subnormal float values may be flushed at any time.
+ // https://www.w3.org/TR/WGSL/#floating-point-evaluation
+ const f32_lhs = f32(lhs);
+ const f32_rhs = f32(rhs);
+ const lhs_options = new Set([f32_lhs, f32(flushSubnormalNumberF32(lhs))]);
+ const rhs_options = new Set([f32_rhs, f32(flushSubnormalNumberF32(rhs))]);
+ const expected: Array<ScalarValue> = [];
+ lhs_options.forEach(l => {
+ rhs_options.forEach(r => {
+ const result = bool(truthFunc(l, r));
+ if (!expected.includes(result)) {
+ expected.push(result);
+ }
+ });
+ });
+
+ return { input: [f32_lhs, f32_rhs], expected: anyOf(...expected) };
+}
+
+export const d = makeCaseCache('binary/f32_logical', {
+ equals_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) === (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ equals_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) === (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ not_equals_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) !== (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ not_equals_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) !== (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_than_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) < (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_than_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) < (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_equals_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) <= (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ less_equals_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) <= (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_than_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) > (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_than_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) > (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_equals_non_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) >= (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+ greater_equals_const: () => {
+ const truthFunc = (lhs: ScalarValue, rhs: ScalarValue): boolean => {
+ return (lhs.value as number) >= (rhs.value as number);
+ };
+
+ return vectorF32Range(2).map(v => {
+ return makeCase(v[0], v[1], truthFunc);
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_comparison.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_comparison.spec.ts
index ef862e7757..42eb8934a4 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_comparison.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_comparison.spec.ts
@@ -4,155 +4,14 @@ Execution Tests for the f32 comparison operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { anyOf } from '../../../../util/compare.js';
-import { bool, f32, Scalar, TypeBool, TypeF32 } from '../../../../util/conversion.js';
-import { flushSubnormalNumberF32, vectorF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, Case, run } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { allInputSources, run } from '../expression.js';
import { binary } from './binary.js';
+import { d } from './f32_comparison.cache.js';
export const g = makeTestGroup(GPUTest);
-/**
- * @returns a test case for the provided left hand & right hand values and truth function.
- * Handles quantization and subnormals.
- */
-function makeCase(
- lhs: number,
- rhs: number,
- truthFunc: (lhs: Scalar, rhs: Scalar) => boolean
-): Case {
- // Subnormal float values may be flushed at any time.
- // https://www.w3.org/TR/WGSL/#floating-point-evaluation
- const f32_lhs = f32(lhs);
- const f32_rhs = f32(rhs);
- const lhs_options = new Set([f32_lhs, f32(flushSubnormalNumberF32(lhs))]);
- const rhs_options = new Set([f32_rhs, f32(flushSubnormalNumberF32(rhs))]);
- const expected: Array<Scalar> = [];
- lhs_options.forEach(l => {
- rhs_options.forEach(r => {
- const result = bool(truthFunc(l, r));
- if (!expected.includes(result)) {
- expected.push(result);
- }
- });
- });
-
- return { input: [f32_lhs, f32_rhs], expected: anyOf(...expected) };
-}
-
-export const d = makeCaseCache('binary/f32_logical', {
- equals_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) === (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- equals_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) === (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- not_equals_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) !== (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- not_equals_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) !== (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_than_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) < (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_than_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) < (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_equals_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) <= (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- less_equals_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) <= (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_than_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) > (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_than_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) > (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_equals_non_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) >= (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
- greater_equals_const: () => {
- const truthFunc = (lhs: Scalar, rhs: Scalar): boolean => {
- return (lhs.value as number) >= (rhs.value as number);
- };
-
- return vectorF32Range(2).map(v => {
- return makeCase(v[0], v[1], truthFunc);
- });
- },
-});
-
g.test('equals')
.specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
.desc(
@@ -168,7 +27,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'equals_const' : 'equals_non_const'
);
- await run(t, binary('=='), [TypeF32, TypeF32], TypeBool, t.params, cases);
+ await run(t, binary('=='), [Type.f32, Type.f32], Type.bool, t.params, cases);
});
g.test('not_equals')
@@ -186,7 +45,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'not_equals_const' : 'not_equals_non_const'
);
- await run(t, binary('!='), [TypeF32, TypeF32], TypeBool, t.params, cases);
+ await run(t, binary('!='), [Type.f32, Type.f32], Type.bool, t.params, cases);
});
g.test('less_than')
@@ -204,7 +63,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'less_than_const' : 'less_than_non_const'
);
- await run(t, binary('<'), [TypeF32, TypeF32], TypeBool, t.params, cases);
+ await run(t, binary('<'), [Type.f32, Type.f32], Type.bool, t.params, cases);
});
g.test('less_equals')
@@ -222,7 +81,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'less_equals_const' : 'less_equals_non_const'
);
- await run(t, binary('<='), [TypeF32, TypeF32], TypeBool, t.params, cases);
+ await run(t, binary('<='), [Type.f32, Type.f32], Type.bool, t.params, cases);
});
g.test('greater_than')
@@ -240,7 +99,7 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'greater_than_const' : 'greater_than_non_const'
);
- await run(t, binary('>'), [TypeF32, TypeF32], TypeBool, t.params, cases);
+ await run(t, binary('>'), [Type.f32, Type.f32], Type.bool, t.params, cases);
});
g.test('greater_equals')
@@ -258,5 +117,5 @@ Accuracy: Correct result
const cases = await d.get(
t.params.inputSource === 'const' ? 'greater_equals_const' : 'greater_equals_non_const'
);
- await run(t, binary('>='), [TypeF32, TypeF32], TypeBool, t.params, cases);
+ await run(t, binary('>='), [Type.f32, Type.f32], Type.bool, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_division.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_division.cache.ts
new file mode 100644
index 0000000000..017f7a451a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_division.cache.ts
@@ -0,0 +1,60 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF32Range, sparseVectorF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const divisionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.divisionInterval(e, s)));
+};
+
+const divisionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.divisionInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ sparseScalarF32Range(),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.divisionInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateVectorScalarToVectorCases(
+ sparseVectorF32Range(dim),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ divisionVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarVectorToVectorCases(
+ sparseScalarF32Range(),
+ sparseVectorF32Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ divisionScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_division', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_division.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_division.spec.ts
index bd3793bf8a..bdd71e69eb 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_division.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_division.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f32 division expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF32Range, sparseVectorF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const divisionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.divisionInterval(e, s)));
-};
-
-const divisionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.divisionInterval(s, e)));
-};
+import { d } from './f32_division.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.divisionInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorScalarToVectorCases(
- sparseVectorF32Range(dim),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- divisionVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarVectorToVectorCases(
- sparseF32Range(),
- sparseVectorF32Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- divisionScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_division', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -84,7 +25,7 @@ Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('/'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, binary('/'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector')
@@ -100,7 +41,7 @@ Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
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);
+ await run(t, binary('/'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('scalar_compound')
@@ -118,7 +59,7 @@ Accuracy: 2.5 ULP for |y| in the range [2^-126, 2^126]
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('/='), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, compoundBinary('/='), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector_scalar')
@@ -138,8 +79,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('/'),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -162,8 +103,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('/='),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -186,8 +127,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('/'),
- [TypeF32, TypeVec(dim, TypeF32)],
- TypeVec(dim, TypeF32),
+ [Type.f32, Type.vec(dim, Type.f32)],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_addition.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_addition.cache.ts
new file mode 100644
index 0000000000..0f3ced975d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_addition.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_[non_]const
+const mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateMatrixPairToMatrixCases(
+ sparseMatrixF32Range(cols, rows),
+ sparseMatrixF32Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.additionMatrixMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_matrix_addition', mat_cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_addition.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_addition.spec.ts
index 9f11c3cac1..06a4ac47cc 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_addition.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_addition.spec.ts
@@ -4,36 +4,14 @@ Execution Tests for matrix f32 addition expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeMat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseMatrixF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f32_matrix_addition.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR_[non_]const
-const mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateMatrixPairToMatrixCases(
- sparseMatrixF32Range(cols, rows),
- sparseMatrixF32Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.additionMatrixMatrixInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_matrix_addition', mat_cases);
-
g.test('matrix')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -57,8 +35,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('+'),
- [TypeMat(cols, rows, TypeF32), TypeMat(cols, rows, TypeF32)],
- TypeMat(cols, rows, TypeF32),
+ [Type.mat(cols, rows, Type.f32), Type.mat(cols, rows, Type.f32)],
+ Type.mat(cols, rows, Type.f32),
t.params,
cases
);
@@ -87,8 +65,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('+='),
- [TypeMat(cols, rows, TypeF32), TypeMat(cols, rows, TypeF32)],
- TypeMat(cols, rows, TypeF32),
+ [Type.mat(cols, rows, Type.f32), Type.mat(cols, rows, Type.f32)],
+ Type.mat(cols, rows, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.cache.ts
new file mode 100644
index 0000000000..cde2d74fdf
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.cache.ts
@@ -0,0 +1,25 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matKxR_matCxK_[non_]const
+const mat_mat_cases = ([2, 3, 4] as const)
+ .flatMap(k =>
+ ([2, 3, 4] as const).flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${k}x${rows}_mat${cols}x${k}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateMatrixPairToMatrixCases(
+ sparseMatrixF32Range(k, rows),
+ sparseMatrixF32Range(cols, k),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.multiplicationMatrixMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_matrix_matrix_multiplication', mat_mat_cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.spec.ts
index 2c48eab187..1ff7799f46 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_matrix_multiplication.spec.ts
@@ -4,38 +4,14 @@ Execution Tests for matrix-matrix f32 multiplication expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeMat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseMatrixF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f32_matrix_matrix_multiplication.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matKxR_matCxK_[non_]const
-const mat_mat_cases = ([2, 3, 4] as const)
- .flatMap(k =>
- ([2, 3, 4] as const).flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${k}x${rows}_mat${cols}x${k}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateMatrixPairToMatrixCases(
- sparseMatrixF32Range(k, rows),
- sparseMatrixF32Range(cols, k),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.multiplicationMatrixMatrixInterval
- );
- },
- }))
- )
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_matrix_matrix_multiplication', mat_mat_cases);
-
g.test('matrix_matrix')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -65,8 +41,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeMat(x_cols, x_rows, TypeF32), TypeMat(y_cols, y_rows, TypeF32)],
- TypeMat(y_cols, x_rows, TypeF32),
+ [Type.mat(x_cols, x_rows, Type.f32), Type.mat(y_cols, y_rows, Type.f32)],
+ Type.mat(y_cols, x_rows, Type.f32),
t.params,
cases
);
@@ -100,8 +76,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('*='),
- [TypeMat(x_cols, x_rows, TypeF32), TypeMat(y_cols, y_rows, TypeF32)],
- TypeMat(y_cols, x_rows, TypeF32),
+ [Type.mat(x_cols, x_rows, Type.f32), Type.mat(y_cols, y_rows, Type.f32)],
+ Type.mat(y_cols, x_rows, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.cache.ts
new file mode 100644
index 0000000000..f17dac31e6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.cache.ts
@@ -0,0 +1,44 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF32Range, sparseScalarF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_scalar_[non_]const
+const mat_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${cols}x${rows}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateMatrixScalarToMatrixCases(
+ sparseMatrixF32Range(cols, rows),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.multiplicationMatrixScalarInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: scalar_matCxR_[non_]const
+const scalar_mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarMatrixToMatrixCases(
+ sparseScalarF32Range(),
+ sparseMatrixF32Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.multiplicationScalarMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_matrix_scalar_multiplication', {
+ ...mat_scalar_cases,
+ ...scalar_mat_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.spec.ts
index f3d36b8382..e8771d19eb 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_scalar_multiplication.spec.ts
@@ -4,57 +4,14 @@ Execution Tests for matrix-scalar and scalar-matrix f32 multiplication expressio
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeMat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseF32Range, sparseMatrixF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f32_matrix_scalar_multiplication.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR_scalar_[non_]const
-const mat_scalar_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${cols}x${rows}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateMatrixScalarToMatrixCases(
- sparseMatrixF32Range(cols, rows),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.multiplicationMatrixScalarInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: scalar_matCxR_[non_]const
-const scalar_mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarMatrixToMatrixCases(
- sparseF32Range(),
- sparseMatrixF32Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.multiplicationScalarMatrixInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_matrix_scalar_multiplication', {
- ...mat_scalar_cases,
- ...scalar_mat_cases,
-});
-
g.test('matrix_scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -80,8 +37,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeMat(cols, rows, TypeF32), TypeF32],
- TypeMat(cols, rows, TypeF32),
+ [Type.mat(cols, rows, Type.f32), Type.f32],
+ Type.mat(cols, rows, Type.f32),
t.params,
cases
);
@@ -112,8 +69,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('*='),
- [TypeMat(cols, rows, TypeF32), TypeF32],
- TypeMat(cols, rows, TypeF32),
+ [Type.mat(cols, rows, Type.f32), Type.f32],
+ Type.mat(cols, rows, Type.f32),
t.params,
cases
);
@@ -144,8 +101,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeF32, TypeMat(cols, rows, TypeF32)],
- TypeMat(cols, rows, TypeF32),
+ [Type.f32, Type.mat(cols, rows, Type.f32)],
+ Type.mat(cols, rows, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.cache.ts
new file mode 100644
index 0000000000..2cd2bc1c6d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_[non_]const
+const mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateMatrixPairToMatrixCases(
+ sparseMatrixF32Range(cols, rows),
+ sparseMatrixF32Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.subtractionMatrixMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_matrix_subtraction', mat_cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.spec.ts
index 5f101d9b27..31565ba598 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_subtraction.spec.ts
@@ -4,36 +4,14 @@ Execution Tests for matrix f32 subtraction expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeMat } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseMatrixF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f32_matrix_subtraction.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR_[non_]const
-const mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateMatrixPairToMatrixCases(
- sparseMatrixF32Range(cols, rows),
- sparseMatrixF32Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.subtractionMatrixMatrixInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_matrix_subtraction', mat_cases);
-
g.test('matrix')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -57,8 +35,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('-'),
- [TypeMat(cols, rows, TypeF32), TypeMat(cols, rows, TypeF32)],
- TypeMat(cols, rows, TypeF32),
+ [Type.mat(cols, rows, Type.f32), Type.mat(cols, rows, Type.f32)],
+ Type.mat(cols, rows, Type.f32),
t.params,
cases
);
@@ -87,8 +65,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('-='),
- [TypeMat(cols, rows, TypeF32), TypeMat(cols, rows, TypeF32)],
- TypeMat(cols, rows, TypeF32),
+ [Type.mat(cols, rows, Type.f32), Type.mat(cols, rows, Type.f32)],
+ Type.mat(cols, rows, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.cache.ts
new file mode 100644
index 0000000000..f20b95029e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.cache.ts
@@ -0,0 +1,44 @@
+import { FP } from '../../../../util/floating_point.js';
+import { sparseMatrixF32Range, sparseVectorF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: matCxR_vecC_[non_]const
+const mat_vec_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`mat${cols}x${rows}_vec${cols}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateMatrixVectorToVectorCases(
+ sparseMatrixF32Range(cols, rows),
+ sparseVectorF32Range(cols),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.multiplicationMatrixVectorInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: vecR_matCxR_[non_]const
+const vec_mat_cases = ([2, 3, 4] as const)
+ .flatMap(rows =>
+ ([2, 3, 4] as const).flatMap(cols =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${rows}_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateVectorMatrixToVectorCases(
+ sparseVectorF32Range(rows),
+ sparseMatrixF32Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.multiplicationVectorMatrixInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_matrix_vector_multiplication', {
+ ...mat_vec_cases,
+ ...vec_mat_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.spec.ts
index e6cdf16d92..8cd7ed49fe 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_matrix_vector_multiplication.spec.ts
@@ -4,57 +4,14 @@ Execution Tests for matrix-vector and vector-matrix f32 multiplication expressio
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeMat, TypeVec } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { sparseMatrixF32Range, sparseVectorF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
+import { d } from './f32_matrix_vector_multiplication.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: matCxR_vecC_[non_]const
-const mat_vec_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`mat${cols}x${rows}_vec${cols}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateMatrixVectorToVectorCases(
- sparseMatrixF32Range(cols, rows),
- sparseVectorF32Range(cols),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.multiplicationMatrixVectorInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: vecR_matCxR_[non_]const
-const vec_mat_cases = ([2, 3, 4] as const)
- .flatMap(rows =>
- ([2, 3, 4] as const).flatMap(cols =>
- ([true, false] as const).map(nonConst => ({
- [`vec${rows}_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorMatrixToVectorCases(
- sparseVectorF32Range(rows),
- sparseMatrixF32Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.multiplicationVectorMatrixInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_matrix_vector_multiplication', {
- ...mat_vec_cases,
- ...vec_mat_cases,
-});
-
g.test('matrix_vector')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -80,8 +37,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeMat(cols, rows, TypeF32), TypeVec(cols, TypeF32)],
- TypeVec(rows, TypeF32),
+ [Type.mat(cols, rows, Type.f32), Type.vec(cols, Type.f32)],
+ Type.vec(rows, Type.f32),
t.params,
cases
);
@@ -112,8 +69,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeVec(rows, TypeF32), TypeMat(cols, rows, TypeF32)],
- TypeVec(cols, TypeF32),
+ [Type.vec(rows, Type.f32), Type.mat(cols, rows, Type.f32)],
+ Type.vec(cols, Type.f32),
t.params,
cases
);
@@ -139,8 +96,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('*='),
- [TypeVec(rows, TypeF32), TypeMat(cols, rows, TypeF32)],
- TypeVec(cols, TypeF32),
+ [Type.vec(rows, Type.f32), Type.mat(cols, rows, Type.f32)],
+ Type.vec(cols, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_multiplication.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_multiplication.cache.ts
new file mode 100644
index 0000000000..6a8c0bd81e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_multiplication.cache.ts
@@ -0,0 +1,60 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF32Range, sparseVectorF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const multiplicationVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.multiplicationInterval(e, s)));
+};
+
+const multiplicationScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.multiplicationInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ sparseScalarF32Range(),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.multiplicationInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateVectorScalarToVectorCases(
+ sparseVectorF32Range(dim),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ multiplicationVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarVectorToVectorCases(
+ sparseScalarF32Range(),
+ sparseVectorF32Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ multiplicationScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_multiplication', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts
index 38da08fd3e..478ca71ef0 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_multiplication.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f32 multiplication expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF32Range, sparseVectorF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const multiplicationVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.multiplicationInterval(e, s)));
-};
-
-const multiplicationScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.multiplicationInterval(s, e)));
-};
+import { d } from './f32_multiplication.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.multiplicationInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorScalarToVectorCases(
- sparseVectorF32Range(dim),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- multiplicationVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarVectorToVectorCases(
- sparseF32Range(),
- sparseVectorF32Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- multiplicationScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_multiplication', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -84,7 +25,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('*'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, binary('*'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector')
@@ -100,7 +41,7 @@ Accuracy: Correctly rounded
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);
+ await run(t, binary('*'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('scalar_compound')
@@ -118,7 +59,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('*='), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, compoundBinary('*='), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector_scalar')
@@ -138,8 +79,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -162,8 +103,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('*='),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -186,8 +127,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('*'),
- [TypeF32, TypeVec(dim, TypeF32)],
- TypeVec(dim, TypeF32),
+ [Type.f32, Type.vec(dim, Type.f32)],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_remainder.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_remainder.cache.ts
new file mode 100644
index 0000000000..2cb1a16f9d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_remainder.cache.ts
@@ -0,0 +1,64 @@
+export const description = `
+Execution Tests for non-matrix f32 remainder expression
+`;
+
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF32Range, sparseVectorF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const remainderVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.remainderInterval(e, s)));
+};
+
+const remainderScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.remainderInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ sparseScalarF32Range(),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.remainderInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateVectorScalarToVectorCases(
+ sparseVectorF32Range(dim),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ remainderVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarVectorToVectorCases(
+ sparseScalarF32Range(),
+ sparseVectorF32Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ remainderScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_remainder', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts
index 390a7f3426..3a9acb02e0 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_remainder.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f32 remainder expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF32Range, sparseVectorF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const remainderVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.remainderInterval(e, s)));
-};
-
-const remainderScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.remainderInterval(s, e)));
-};
+import { d } from './f32_remainder.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.remainderInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorScalarToVectorCases(
- sparseVectorF32Range(dim),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- remainderVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarVectorToVectorCases(
- sparseF32Range(),
- sparseVectorF32Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- remainderScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_remainder', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -84,7 +25,7 @@ Accuracy: Derived from x - y * trunc(x/y)
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('%'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, binary('%'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector')
@@ -100,7 +41,7 @@ Accuracy: Derived from x - y * trunc(x/y)
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);
+ await run(t, binary('%'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('scalar_compound')
@@ -118,7 +59,7 @@ Accuracy: Derived from x - y * trunc(x/y)
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('%='), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, compoundBinary('%='), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector_scalar')
@@ -138,8 +79,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('%'),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -162,8 +103,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('%='),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -186,8 +127,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('%'),
- [TypeF32, TypeVec(dim, TypeF32)],
- TypeVec(dim, TypeF32),
+ [Type.f32, Type.vec(dim, Type.f32)],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_subtraction.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_subtraction.cache.ts
new file mode 100644
index 0000000000..519c0b3783
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_subtraction.cache.ts
@@ -0,0 +1,60 @@
+import { FP, FPVector } from '../../../../util/floating_point.js';
+import { sparseScalarF32Range, sparseVectorF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const subtractionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.subtractionInterval(e, s)));
+};
+
+const subtractionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
+ return FP.f32.toVector(v.map(e => FP.f32.subtractionInterval(s, e)));
+};
+
+const scalar_cases = ([true, false] as const)
+ .map(nonConst => ({
+ [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ sparseScalarF32Range(),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.subtractionInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const vector_scalar_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateVectorScalarToVectorCases(
+ sparseVectorF32Range(dim),
+ sparseScalarF32Range(),
+ nonConst ? 'unfiltered' : 'finite',
+ subtractionVectorScalarInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const scalar_vector_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateScalarVectorToVectorCases(
+ sparseScalarF32Range(),
+ sparseVectorF32Range(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ subtractionScalarVectorInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('binary/f32_subtraction', {
+ ...scalar_cases,
+ ...vector_scalar_cases,
+ ...scalar_vector_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts
index 91e06b7de8..55097390e9 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/f32_subtraction.spec.ts
@@ -4,73 +4,14 @@ Execution Tests for non-matrix f32 subtraction expression
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32, TypeVec } from '../../../../util/conversion.js';
-import { FP, FPVector } from '../../../../util/floating_point.js';
-import { sparseF32Range, sparseVectorF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-const subtractionVectorScalarInterval = (v: readonly number[], s: number): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.subtractionInterval(e, s)));
-};
-
-const subtractionScalarVectorInterval = (s: number, v: readonly number[]): FPVector => {
- return FP.f32.toVector(v.map(e => FP.f32.subtractionInterval(s, e)));
-};
+import { d } from './f32_subtraction.cache.js';
export const g = makeTestGroup(GPUTest);
-const scalar_cases = ([true, false] as const)
- .map(nonConst => ({
- [`scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.subtractionInterval
- );
- },
- }))
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const vector_scalar_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorScalarToVectorCases(
- sparseVectorF32Range(dim),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- subtractionVectorScalarInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-const scalar_vector_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`scalar_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateScalarVectorToVectorCases(
- sparseF32Range(),
- sparseVectorF32Range(dim),
- nonConst ? 'unfiltered' : 'finite',
- subtractionScalarVectorInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('binary/f32_subtraction', {
- ...scalar_cases,
- ...vector_scalar_cases,
- ...scalar_vector_cases,
-});
-
g.test('scalar')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -84,7 +25,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, binary('-'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, binary('-'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector')
@@ -100,7 +41,7 @@ Accuracy: Correctly rounded
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);
+ await run(t, binary('-'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('scalar_compound')
@@ -118,7 +59,7 @@ Accuracy: Correctly rounded
const cases = await d.get(
t.params.inputSource === 'const' ? 'scalar_const' : 'scalar_non_const'
);
- await run(t, compoundBinary('-='), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, compoundBinary('-='), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('vector_scalar')
@@ -138,8 +79,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('-'),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -162,8 +103,8 @@ Accuracy: Correctly rounded
await run(
t,
compoundBinary('-='),
- [TypeVec(dim, TypeF32), TypeF32],
- TypeVec(dim, TypeF32),
+ [Type.vec(dim, Type.f32), Type.f32],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
@@ -186,8 +127,8 @@ Accuracy: Correctly rounded
await run(
t,
binary('-'),
- [TypeF32, TypeVec(dim, TypeF32)],
- TypeVec(dim, TypeF32),
+ [Type.f32, Type.vec(dim, Type.f32)],
+ Type.vec(dim, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_arithmetic.cache.ts
new file mode 100644
index 0000000000..ca7ca879f5
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_arithmetic.cache.ts
@@ -0,0 +1,306 @@
+import { kValue } from '../../../../util/constants.js';
+import { sparseI32Range, vectorI32Range } from '../../../../util/math.js';
+import {
+ generateBinaryToI32Cases,
+ generateI32VectorBinaryToVectorCases,
+ generateVectorI32BinaryToVectorCases,
+} from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+function i32_add(x: number, y: number): number | undefined {
+ return x + y;
+}
+
+function i32_subtract(x: number, y: number): number | undefined {
+ return x - y;
+}
+
+function i32_multiply(x: number, y: number): number | undefined {
+ return Math.imul(x, y);
+}
+
+function i32_divide_non_const(x: number, y: number): number | undefined {
+ if (y === 0) {
+ return x;
+ }
+ if (x === kValue.i32.negative.min && y === -1) {
+ return x;
+ }
+ return x / y;
+}
+
+function i32_divide_const(x: number, y: number): number | undefined {
+ if (y === 0) {
+ return undefined;
+ }
+ if (x === kValue.i32.negative.min && y === -1) {
+ return undefined;
+ }
+ return x / y;
+}
+
+function i32_remainder_non_const(x: number, y: number): number | undefined {
+ if (y === 0) {
+ return 0;
+ }
+ if (x === kValue.i32.negative.min && y === -1) {
+ return 0;
+ }
+ return x % y;
+}
+
+function i32_remainder_const(x: number, y: number): number | undefined {
+ if (y === 0) {
+ return undefined;
+ }
+ if (x === kValue.i32.negative.min && y === -1) {
+ return undefined;
+ }
+ return x % y;
+}
+
+export const d = makeCaseCache('binary/i32_arithmetic', {
+ addition: () => {
+ return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_add);
+ },
+ subtraction: () => {
+ return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_subtract);
+ },
+ multiplication: () => {
+ return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_multiply);
+ },
+ division_non_const: () => {
+ return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_divide_non_const);
+ },
+ division_const: () => {
+ return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_divide_const);
+ },
+ remainder_non_const: () => {
+ return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_remainder_non_const);
+ },
+ remainder_const: () => {
+ return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_remainder_const);
+ },
+ addition_scalar_vector2: () => {
+ return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(2), i32_add);
+ },
+ addition_scalar_vector3: () => {
+ return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(3), i32_add);
+ },
+ addition_scalar_vector4: () => {
+ return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(4), i32_add);
+ },
+ addition_vector2_scalar: () => {
+ return generateVectorI32BinaryToVectorCases(vectorI32Range(2), sparseI32Range(), i32_add);
+ },
+ addition_vector3_scalar: () => {
+ return generateVectorI32BinaryToVectorCases(vectorI32Range(3), sparseI32Range(), i32_add);
+ },
+ addition_vector4_scalar: () => {
+ return generateVectorI32BinaryToVectorCases(vectorI32Range(4), sparseI32Range(), i32_add);
+ },
+ subtraction_scalar_vector2: () => {
+ return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(2), i32_subtract);
+ },
+ subtraction_scalar_vector3: () => {
+ return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(3), i32_subtract);
+ },
+ subtraction_scalar_vector4: () => {
+ return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(4), i32_subtract);
+ },
+ subtraction_vector2_scalar: () => {
+ return generateVectorI32BinaryToVectorCases(vectorI32Range(2), sparseI32Range(), i32_subtract);
+ },
+ subtraction_vector3_scalar: () => {
+ return generateVectorI32BinaryToVectorCases(vectorI32Range(3), sparseI32Range(), i32_subtract);
+ },
+ subtraction_vector4_scalar: () => {
+ return generateVectorI32BinaryToVectorCases(vectorI32Range(4), sparseI32Range(), i32_subtract);
+ },
+ multiplication_scalar_vector2: () => {
+ return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(2), i32_multiply);
+ },
+ multiplication_scalar_vector3: () => {
+ return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(3), i32_multiply);
+ },
+ multiplication_scalar_vector4: () => {
+ return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(4), i32_multiply);
+ },
+ multiplication_vector2_scalar: () => {
+ return generateVectorI32BinaryToVectorCases(vectorI32Range(2), sparseI32Range(), i32_multiply);
+ },
+ multiplication_vector3_scalar: () => {
+ return generateVectorI32BinaryToVectorCases(vectorI32Range(3), sparseI32Range(), i32_multiply);
+ },
+ multiplication_vector4_scalar: () => {
+ return generateVectorI32BinaryToVectorCases(vectorI32Range(4), sparseI32Range(), i32_multiply);
+ },
+ division_scalar_vector2_non_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(2),
+ i32_divide_non_const
+ );
+ },
+ division_scalar_vector3_non_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(3),
+ i32_divide_non_const
+ );
+ },
+ division_scalar_vector4_non_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(4),
+ i32_divide_non_const
+ );
+ },
+ division_vector2_scalar_non_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(2),
+ sparseI32Range(),
+ i32_divide_non_const
+ );
+ },
+ division_vector3_scalar_non_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(3),
+ sparseI32Range(),
+ i32_divide_non_const
+ );
+ },
+ division_vector4_scalar_non_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(4),
+ sparseI32Range(),
+ i32_divide_non_const
+ );
+ },
+ division_scalar_vector2_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(2),
+ i32_divide_const
+ );
+ },
+ division_scalar_vector3_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(3),
+ i32_divide_const
+ );
+ },
+ division_scalar_vector4_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(4),
+ i32_divide_const
+ );
+ },
+ division_vector2_scalar_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(2),
+ sparseI32Range(),
+ i32_divide_const
+ );
+ },
+ division_vector3_scalar_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(3),
+ sparseI32Range(),
+ i32_divide_const
+ );
+ },
+ division_vector4_scalar_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(4),
+ sparseI32Range(),
+ i32_divide_const
+ );
+ },
+ remainder_scalar_vector2_non_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(2),
+ i32_remainder_non_const
+ );
+ },
+ remainder_scalar_vector3_non_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(3),
+ i32_remainder_non_const
+ );
+ },
+ remainder_scalar_vector4_non_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(4),
+ i32_remainder_non_const
+ );
+ },
+ remainder_vector2_scalar_non_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(2),
+ sparseI32Range(),
+ i32_remainder_non_const
+ );
+ },
+ remainder_vector3_scalar_non_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(3),
+ sparseI32Range(),
+ i32_remainder_non_const
+ );
+ },
+ remainder_vector4_scalar_non_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(4),
+ sparseI32Range(),
+ i32_remainder_non_const
+ );
+ },
+ remainder_scalar_vector2_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(2),
+ i32_remainder_const
+ );
+ },
+ remainder_scalar_vector3_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(3),
+ i32_remainder_const
+ );
+ },
+ remainder_scalar_vector4_const: () => {
+ return generateI32VectorBinaryToVectorCases(
+ sparseI32Range(),
+ vectorI32Range(4),
+ i32_remainder_const
+ );
+ },
+ remainder_vector2_scalar_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(2),
+ sparseI32Range(),
+ i32_remainder_const
+ );
+ },
+ remainder_vector3_scalar_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(3),
+ sparseI32Range(),
+ i32_remainder_const
+ );
+ },
+ remainder_vector4_scalar_const: () => {
+ return generateVectorI32BinaryToVectorCases(
+ vectorI32Range(4),
+ sparseI32Range(),
+ i32_remainder_const
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_arithmetic.spec.ts
index e9b7a2407f..ef8ea00447 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_arithmetic.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_arithmetic.spec.ts
@@ -4,320 +4,14 @@ Execution Tests for the i32 arithmetic binary expression operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { kValue } from '../../../../util/constants.js';
-import { TypeI32, TypeVec } from '../../../../util/conversion.js';
-import { sparseI32Range, vectorI32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import {
- allInputSources,
- generateBinaryToI32Cases,
- generateI32VectorBinaryToVectorCases,
- generateVectorI32BinaryToVectorCases,
- run,
-} from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-function i32_add(x: number, y: number): number | undefined {
- return x + y;
-}
-
-function i32_subtract(x: number, y: number): number | undefined {
- return x - y;
-}
-
-function i32_multiply(x: number, y: number): number | undefined {
- return Math.imul(x, y);
-}
-
-function i32_divide_non_const(x: number, y: number): number | undefined {
- if (y === 0) {
- return x;
- }
- if (x === kValue.i32.negative.min && y === -1) {
- return x;
- }
- return x / y;
-}
-
-function i32_divide_const(x: number, y: number): number | undefined {
- if (y === 0) {
- return undefined;
- }
- if (x === kValue.i32.negative.min && y === -1) {
- return undefined;
- }
- return x / y;
-}
-
-function i32_remainder_non_const(x: number, y: number): number | undefined {
- if (y === 0) {
- return 0;
- }
- if (x === kValue.i32.negative.min && y === -1) {
- return 0;
- }
- return x % y;
-}
-
-function i32_remainder_const(x: number, y: number): number | undefined {
- if (y === 0) {
- return undefined;
- }
- if (x === kValue.i32.negative.min && y === -1) {
- return undefined;
- }
- return x % y;
-}
+import { d } from './i32_arithmetic.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('binary/i32_arithmetic', {
- addition: () => {
- return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_add);
- },
- subtraction: () => {
- return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_subtract);
- },
- multiplication: () => {
- return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_multiply);
- },
- division_non_const: () => {
- return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_divide_non_const);
- },
- division_const: () => {
- return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_divide_const);
- },
- remainder_non_const: () => {
- return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_remainder_non_const);
- },
- remainder_const: () => {
- return generateBinaryToI32Cases(sparseI32Range(), sparseI32Range(), i32_remainder_const);
- },
- addition_scalar_vector2: () => {
- return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(2), i32_add);
- },
- addition_scalar_vector3: () => {
- return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(3), i32_add);
- },
- addition_scalar_vector4: () => {
- return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(4), i32_add);
- },
- addition_vector2_scalar: () => {
- return generateVectorI32BinaryToVectorCases(vectorI32Range(2), sparseI32Range(), i32_add);
- },
- addition_vector3_scalar: () => {
- return generateVectorI32BinaryToVectorCases(vectorI32Range(3), sparseI32Range(), i32_add);
- },
- addition_vector4_scalar: () => {
- return generateVectorI32BinaryToVectorCases(vectorI32Range(4), sparseI32Range(), i32_add);
- },
- subtraction_scalar_vector2: () => {
- return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(2), i32_subtract);
- },
- subtraction_scalar_vector3: () => {
- return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(3), i32_subtract);
- },
- subtraction_scalar_vector4: () => {
- return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(4), i32_subtract);
- },
- subtraction_vector2_scalar: () => {
- return generateVectorI32BinaryToVectorCases(vectorI32Range(2), sparseI32Range(), i32_subtract);
- },
- subtraction_vector3_scalar: () => {
- return generateVectorI32BinaryToVectorCases(vectorI32Range(3), sparseI32Range(), i32_subtract);
- },
- subtraction_vector4_scalar: () => {
- return generateVectorI32BinaryToVectorCases(vectorI32Range(4), sparseI32Range(), i32_subtract);
- },
- multiplication_scalar_vector2: () => {
- return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(2), i32_multiply);
- },
- multiplication_scalar_vector3: () => {
- return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(3), i32_multiply);
- },
- multiplication_scalar_vector4: () => {
- return generateI32VectorBinaryToVectorCases(sparseI32Range(), vectorI32Range(4), i32_multiply);
- },
- multiplication_vector2_scalar: () => {
- return generateVectorI32BinaryToVectorCases(vectorI32Range(2), sparseI32Range(), i32_multiply);
- },
- multiplication_vector3_scalar: () => {
- return generateVectorI32BinaryToVectorCases(vectorI32Range(3), sparseI32Range(), i32_multiply);
- },
- multiplication_vector4_scalar: () => {
- return generateVectorI32BinaryToVectorCases(vectorI32Range(4), sparseI32Range(), i32_multiply);
- },
- division_scalar_vector2_non_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(2),
- i32_divide_non_const
- );
- },
- division_scalar_vector3_non_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(3),
- i32_divide_non_const
- );
- },
- division_scalar_vector4_non_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(4),
- i32_divide_non_const
- );
- },
- division_vector2_scalar_non_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(2),
- sparseI32Range(),
- i32_divide_non_const
- );
- },
- division_vector3_scalar_non_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(3),
- sparseI32Range(),
- i32_divide_non_const
- );
- },
- division_vector4_scalar_non_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(4),
- sparseI32Range(),
- i32_divide_non_const
- );
- },
- division_scalar_vector2_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(2),
- i32_divide_const
- );
- },
- division_scalar_vector3_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(3),
- i32_divide_const
- );
- },
- division_scalar_vector4_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(4),
- i32_divide_const
- );
- },
- division_vector2_scalar_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(2),
- sparseI32Range(),
- i32_divide_const
- );
- },
- division_vector3_scalar_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(3),
- sparseI32Range(),
- i32_divide_const
- );
- },
- division_vector4_scalar_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(4),
- sparseI32Range(),
- i32_divide_const
- );
- },
- remainder_scalar_vector2_non_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(2),
- i32_remainder_non_const
- );
- },
- remainder_scalar_vector3_non_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(3),
- i32_remainder_non_const
- );
- },
- remainder_scalar_vector4_non_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(4),
- i32_remainder_non_const
- );
- },
- remainder_vector2_scalar_non_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(2),
- sparseI32Range(),
- i32_remainder_non_const
- );
- },
- remainder_vector3_scalar_non_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(3),
- sparseI32Range(),
- i32_remainder_non_const
- );
- },
- remainder_vector4_scalar_non_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(4),
- sparseI32Range(),
- i32_remainder_non_const
- );
- },
- remainder_scalar_vector2_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(2),
- i32_remainder_const
- );
- },
- remainder_scalar_vector3_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(3),
- i32_remainder_const
- );
- },
- remainder_scalar_vector4_const: () => {
- return generateI32VectorBinaryToVectorCases(
- sparseI32Range(),
- vectorI32Range(4),
- i32_remainder_const
- );
- },
- remainder_vector2_scalar_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(2),
- sparseI32Range(),
- i32_remainder_const
- );
- },
- remainder_vector3_scalar_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(3),
- sparseI32Range(),
- i32_remainder_const
- );
- },
- remainder_vector4_scalar_const: () => {
- return generateVectorI32BinaryToVectorCases(
- vectorI32Range(4),
- sparseI32Range(),
- i32_remainder_const
- );
- },
-});
-
g.test('addition')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -330,7 +24,7 @@ Expression: x + y
)
.fn(async t => {
const cases = await d.get('addition');
- await run(t, binary('+'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, binary('+'), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('addition_compound')
@@ -345,7 +39,7 @@ Expression: x += y
)
.fn(async t => {
const cases = await d.get('addition');
- await run(t, compoundBinary('+='), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, compoundBinary('+='), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('subtraction')
@@ -360,7 +54,7 @@ Expression: x - y
)
.fn(async t => {
const cases = await d.get('subtraction');
- await run(t, binary('-'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, binary('-'), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('subtraction_compound')
@@ -375,7 +69,7 @@ Expression: x -= y
)
.fn(async t => {
const cases = await d.get('subtraction');
- await run(t, compoundBinary('-='), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, compoundBinary('-='), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('multiplication')
@@ -390,7 +84,7 @@ Expression: x * y
)
.fn(async t => {
const cases = await d.get('multiplication');
- await run(t, binary('*'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, binary('*'), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('multiplication_compound')
@@ -405,7 +99,7 @@ Expression: x *= y
)
.fn(async t => {
const cases = await d.get('multiplication');
- await run(t, compoundBinary('*='), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, compoundBinary('*='), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('division')
@@ -422,7 +116,7 @@ Expression: x / y
const cases = await d.get(
t.params.inputSource === 'const' ? 'division_const' : 'division_non_const'
);
- await run(t, binary('/'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, binary('/'), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('division_compound')
@@ -439,7 +133,7 @@ Expression: x /= y
const cases = await d.get(
t.params.inputSource === 'const' ? 'division_const' : 'division_non_const'
);
- await run(t, compoundBinary('/='), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, compoundBinary('/='), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('remainder')
@@ -456,7 +150,7 @@ Expression: x % y
const cases = await d.get(
t.params.inputSource === 'const' ? 'remainder_const' : 'remainder_non_const'
);
- await run(t, binary('%'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, binary('%'), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('remainder_compound')
@@ -473,7 +167,7 @@ Expression: x %= y
const cases = await d.get(
t.params.inputSource === 'const' ? 'remainder_const' : 'remainder_non_const'
);
- await run(t, compoundBinary('%='), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, compoundBinary('%='), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('addition_scalar_vector')
@@ -488,9 +182,9 @@ Expression: x + y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const cases = await d.get(`addition_scalar_vector${vec_size}`);
- await run(t, binary('+'), [TypeI32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('+'), [Type.i32, vec_type], vec_type, t.params, cases);
});
g.test('addition_vector_scalar')
@@ -505,9 +199,9 @@ Expression: x + y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const cases = await d.get(`addition_vector${vec_size}_scalar`);
- await run(t, binary('+'), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, binary('+'), [vec_type, Type.i32], vec_type, t.params, cases);
});
g.test('addition_vector_scalar_compound')
@@ -522,9 +216,9 @@ Expression: x += y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const cases = await d.get(`addition_vector${vec_size}_scalar`);
- await run(t, compoundBinary('+='), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, compoundBinary('+='), [vec_type, Type.i32], vec_type, t.params, cases);
});
g.test('subtraction_scalar_vector')
@@ -539,9 +233,9 @@ Expression: x - y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const cases = await d.get(`subtraction_scalar_vector${vec_size}`);
- await run(t, binary('-'), [TypeI32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('-'), [Type.i32, vec_type], vec_type, t.params, cases);
});
g.test('subtraction_vector_scalar')
@@ -556,9 +250,9 @@ Expression: x - y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const cases = await d.get(`subtraction_vector${vec_size}_scalar`);
- await run(t, binary('-'), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, binary('-'), [vec_type, Type.i32], vec_type, t.params, cases);
});
g.test('subtraction_vector_scalar_compound')
@@ -573,9 +267,9 @@ Expression: x -= y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const cases = await d.get(`subtraction_vector${vec_size}_scalar`);
- await run(t, compoundBinary('-='), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, compoundBinary('-='), [vec_type, Type.i32], vec_type, t.params, cases);
});
g.test('multiplication_scalar_vector')
@@ -590,9 +284,9 @@ Expression: x * y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const cases = await d.get(`multiplication_scalar_vector${vec_size}`);
- await run(t, binary('*'), [TypeI32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('*'), [Type.i32, vec_type], vec_type, t.params, cases);
});
g.test('multiplication_vector_scalar')
@@ -607,9 +301,9 @@ Expression: x * y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const cases = await d.get(`multiplication_vector${vec_size}_scalar`);
- await run(t, binary('*'), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, binary('*'), [vec_type, Type.i32], vec_type, t.params, cases);
});
g.test('multiplication_vector_scalar_compound')
@@ -624,9 +318,9 @@ Expression: x *= y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const cases = await d.get(`multiplication_vector${vec_size}_scalar`);
- await run(t, compoundBinary('*='), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, compoundBinary('*='), [vec_type, Type.i32], vec_type, t.params, cases);
});
g.test('division_scalar_vector')
@@ -641,10 +335,10 @@ Expression: x / y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`division_scalar_vector${vec_size}_${source}`);
- await run(t, binary('/'), [TypeI32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('/'), [Type.i32, vec_type], vec_type, t.params, cases);
});
g.test('division_vector_scalar')
@@ -659,10 +353,10 @@ Expression: x / y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`division_vector${vec_size}_scalar_${source}`);
- await run(t, binary('/'), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, binary('/'), [vec_type, Type.i32], vec_type, t.params, cases);
});
g.test('division_vector_scalar_compound')
@@ -677,10 +371,10 @@ Expression: x /= y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`division_vector${vec_size}_scalar_${source}`);
- await run(t, compoundBinary('/='), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, compoundBinary('/='), [vec_type, Type.i32], vec_type, t.params, cases);
});
g.test('remainder_scalar_vector')
@@ -695,10 +389,10 @@ Expression: x % y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`remainder_scalar_vector${vec_size}_${source}`);
- await run(t, binary('%'), [TypeI32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('%'), [Type.i32, vec_type], vec_type, t.params, cases);
});
g.test('remainder_vector_scalar')
@@ -713,10 +407,10 @@ Expression: x % y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`remainder_vector${vec_size}_scalar_${source}`);
- await run(t, binary('%'), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, binary('%'), [vec_type, Type.i32], vec_type, t.params, cases);
});
g.test('remainder_vector_scalar_compound')
@@ -731,8 +425,8 @@ Expression: x %= y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeI32);
+ const vec_type = Type.vec(vec_size, Type.i32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`remainder_vector${vec_size}_scalar_${source}`);
- await run(t, compoundBinary('%='), [vec_type, TypeI32], vec_type, t.params, cases);
+ await run(t, compoundBinary('%='), [vec_type, Type.i32], vec_type, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_comparison.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_comparison.cache.ts
new file mode 100644
index 0000000000..de0a6de477
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_comparison.cache.ts
@@ -0,0 +1,21 @@
+import { bool, i32 } from '../../../../util/conversion.js';
+import { vectorI32Range } from '../../../../util/math.js';
+import { Case } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+/**
+ * @returns a test case for the provided left hand & right hand values and
+ * expected boolean result.
+ */
+function makeCase(lhs: number, rhs: number, expected_answer: boolean): Case {
+ return { input: [i32(lhs), i32(rhs)], expected: bool(expected_answer) };
+}
+
+export const d = makeCaseCache('binary/i32_comparison', {
+ equals: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] === v[1])),
+ not_equals: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] !== v[1])),
+ less_than: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] < v[1])),
+ less_equal: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] <= v[1])),
+ greater_than: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] > v[1])),
+ greater_equal: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] >= v[1])),
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_comparison.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_comparison.spec.ts
index dce1a2519e..9b6566c9a4 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_comparison.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/i32_comparison.spec.ts
@@ -4,32 +4,14 @@ Execution Tests for the i32 comparison expressions
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { i32, bool, TypeBool, TypeI32 } from '../../../../util/conversion.js';
-import { vectorI32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, Case, run } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { allInputSources, run } from '../expression.js';
import { binary } from './binary.js';
+import { d } from './i32_comparison.cache.js';
export const g = makeTestGroup(GPUTest);
-/**
- * @returns a test case for the provided left hand & right hand values and
- * expected boolean result.
- */
-function makeCase(lhs: number, rhs: number, expected_answer: boolean): Case {
- return { input: [i32(lhs), i32(rhs)], expected: bool(expected_answer) };
-}
-
-export const d = makeCaseCache('binary/i32_comparison', {
- equals: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] === v[1])),
- not_equals: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] !== v[1])),
- less_than: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] < v[1])),
- less_equal: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] <= v[1])),
- greater_than: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] > v[1])),
- greater_equal: () => vectorI32Range(2).map(v => makeCase(v[0], v[1], v[0] >= v[1])),
-});
-
g.test('equals')
.specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
.desc(
@@ -42,7 +24,7 @@ Expression: x == y
)
.fn(async t => {
const cases = await d.get('equals');
- await run(t, binary('=='), [TypeI32, TypeI32], TypeBool, t.params, cases);
+ await run(t, binary('=='), [Type.i32, Type.i32], Type.bool, t.params, cases);
});
g.test('not_equals')
@@ -57,7 +39,7 @@ Expression: x != y
)
.fn(async t => {
const cases = await d.get('not_equals');
- await run(t, binary('!='), [TypeI32, TypeI32], TypeBool, t.params, cases);
+ await run(t, binary('!='), [Type.i32, Type.i32], Type.bool, t.params, cases);
});
g.test('less_than')
@@ -72,7 +54,7 @@ Expression: x < y
)
.fn(async t => {
const cases = await d.get('less_than');
- await run(t, binary('<'), [TypeI32, TypeI32], TypeBool, t.params, cases);
+ await run(t, binary('<'), [Type.i32, Type.i32], Type.bool, t.params, cases);
});
g.test('less_equals')
@@ -87,7 +69,7 @@ Expression: x <= y
)
.fn(async t => {
const cases = await d.get('less_equal');
- await run(t, binary('<='), [TypeI32, TypeI32], TypeBool, t.params, cases);
+ await run(t, binary('<='), [Type.i32, Type.i32], Type.bool, t.params, cases);
});
g.test('greater_than')
@@ -102,7 +84,7 @@ Expression: x > y
)
.fn(async t => {
const cases = await d.get('greater_than');
- await run(t, binary('>'), [TypeI32, TypeI32], TypeBool, t.params, cases);
+ await run(t, binary('>'), [Type.i32, Type.i32], Type.bool, t.params, cases);
});
g.test('greater_equals')
@@ -117,5 +99,5 @@ Expression: x >= y
)
.fn(async t => {
const cases = await d.get('greater_equal');
- await run(t, binary('>='), [TypeI32, TypeI32], TypeBool, t.params, cases);
+ await run(t, binary('>='), [Type.i32, Type.i32], Type.bool, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_arithmetic.cache.ts
new file mode 100644
index 0000000000..91be35a1ee
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_arithmetic.cache.ts
@@ -0,0 +1,293 @@
+import { sparseU32Range, vectorU32Range } from '../../../../util/math.js';
+import {
+ generateBinaryToU32Cases,
+ generateU32VectorBinaryToVectorCases,
+ generateVectorU32BinaryToVectorCases,
+} from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+function u32_add(x: number, y: number): number | undefined {
+ return x + y;
+}
+
+function u32_subtract(x: number, y: number): number | undefined {
+ return x - y;
+}
+
+function u32_multiply(x: number, y: number): number | undefined {
+ return Math.imul(x, y);
+}
+
+function u32_divide_non_const(x: number, y: number): number | undefined {
+ if (y === 0) {
+ return x;
+ }
+ return x / y;
+}
+
+function u32_divide_const(x: number, y: number): number | undefined {
+ if (y === 0) {
+ return undefined;
+ }
+ return x / y;
+}
+
+function u32_remainder_non_const(x: number, y: number): number | undefined {
+ if (y === 0) {
+ return 0;
+ }
+ return x % y;
+}
+
+function u32_remainder_const(x: number, y: number): number | undefined {
+ if (y === 0) {
+ return undefined;
+ }
+ return x % y;
+}
+
+export const d = makeCaseCache('binary/u32_arithmetic', {
+ addition: () => {
+ return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_add);
+ },
+ subtraction: () => {
+ return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_subtract);
+ },
+ multiplication: () => {
+ return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_multiply);
+ },
+ division_non_const: () => {
+ return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_divide_non_const);
+ },
+ division_const: () => {
+ return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_divide_const);
+ },
+ remainder_non_const: () => {
+ return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_remainder_non_const);
+ },
+ remainder_const: () => {
+ return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_remainder_const);
+ },
+ addition_scalar_vector2: () => {
+ return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(2), u32_add);
+ },
+ addition_scalar_vector3: () => {
+ return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(3), u32_add);
+ },
+ addition_scalar_vector4: () => {
+ return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(4), u32_add);
+ },
+ addition_vector2_scalar: () => {
+ return generateVectorU32BinaryToVectorCases(vectorU32Range(2), sparseU32Range(), u32_add);
+ },
+ addition_vector3_scalar: () => {
+ return generateVectorU32BinaryToVectorCases(vectorU32Range(3), sparseU32Range(), u32_add);
+ },
+ addition_vector4_scalar: () => {
+ return generateVectorU32BinaryToVectorCases(vectorU32Range(4), sparseU32Range(), u32_add);
+ },
+ subtraction_scalar_vector2: () => {
+ return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(2), u32_subtract);
+ },
+ subtraction_scalar_vector3: () => {
+ return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(3), u32_subtract);
+ },
+ subtraction_scalar_vector4: () => {
+ return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(4), u32_subtract);
+ },
+ subtraction_vector2_scalar: () => {
+ return generateVectorU32BinaryToVectorCases(vectorU32Range(2), sparseU32Range(), u32_subtract);
+ },
+ subtraction_vector3_scalar: () => {
+ return generateVectorU32BinaryToVectorCases(vectorU32Range(3), sparseU32Range(), u32_subtract);
+ },
+ subtraction_vector4_scalar: () => {
+ return generateVectorU32BinaryToVectorCases(vectorU32Range(4), sparseU32Range(), u32_subtract);
+ },
+ multiplication_scalar_vector2: () => {
+ return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(2), u32_multiply);
+ },
+ multiplication_scalar_vector3: () => {
+ return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(3), u32_multiply);
+ },
+ multiplication_scalar_vector4: () => {
+ return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(4), u32_multiply);
+ },
+ multiplication_vector2_scalar: () => {
+ return generateVectorU32BinaryToVectorCases(vectorU32Range(2), sparseU32Range(), u32_multiply);
+ },
+ multiplication_vector3_scalar: () => {
+ return generateVectorU32BinaryToVectorCases(vectorU32Range(3), sparseU32Range(), u32_multiply);
+ },
+ multiplication_vector4_scalar: () => {
+ return generateVectorU32BinaryToVectorCases(vectorU32Range(4), sparseU32Range(), u32_multiply);
+ },
+ division_scalar_vector2_non_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(2),
+ u32_divide_non_const
+ );
+ },
+ division_scalar_vector3_non_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(3),
+ u32_divide_non_const
+ );
+ },
+ division_scalar_vector4_non_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(4),
+ u32_divide_non_const
+ );
+ },
+ division_vector2_scalar_non_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(2),
+ sparseU32Range(),
+ u32_divide_non_const
+ );
+ },
+ division_vector3_scalar_non_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(3),
+ sparseU32Range(),
+ u32_divide_non_const
+ );
+ },
+ division_vector4_scalar_non_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(4),
+ sparseU32Range(),
+ u32_divide_non_const
+ );
+ },
+ division_scalar_vector2_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(2),
+ u32_divide_const
+ );
+ },
+ division_scalar_vector3_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(3),
+ u32_divide_const
+ );
+ },
+ division_scalar_vector4_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(4),
+ u32_divide_const
+ );
+ },
+ division_vector2_scalar_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(2),
+ sparseU32Range(),
+ u32_divide_const
+ );
+ },
+ division_vector3_scalar_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(3),
+ sparseU32Range(),
+ u32_divide_const
+ );
+ },
+ division_vector4_scalar_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(4),
+ sparseU32Range(),
+ u32_divide_const
+ );
+ },
+ remainder_scalar_vector2_non_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(2),
+ u32_remainder_non_const
+ );
+ },
+ remainder_scalar_vector3_non_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(3),
+ u32_remainder_non_const
+ );
+ },
+ remainder_scalar_vector4_non_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(4),
+ u32_remainder_non_const
+ );
+ },
+ remainder_vector2_scalar_non_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(2),
+ sparseU32Range(),
+ u32_remainder_non_const
+ );
+ },
+ remainder_vector3_scalar_non_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(3),
+ sparseU32Range(),
+ u32_remainder_non_const
+ );
+ },
+ remainder_vector4_scalar_non_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(4),
+ sparseU32Range(),
+ u32_remainder_non_const
+ );
+ },
+ remainder_scalar_vector2_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(2),
+ u32_remainder_const
+ );
+ },
+ remainder_scalar_vector3_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(3),
+ u32_remainder_const
+ );
+ },
+ remainder_scalar_vector4_const: () => {
+ return generateU32VectorBinaryToVectorCases(
+ sparseU32Range(),
+ vectorU32Range(4),
+ u32_remainder_const
+ );
+ },
+ remainder_vector2_scalar_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(2),
+ sparseU32Range(),
+ u32_remainder_const
+ );
+ },
+ remainder_vector3_scalar_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(3),
+ sparseU32Range(),
+ u32_remainder_const
+ );
+ },
+ remainder_vector4_scalar_const: () => {
+ return generateVectorU32BinaryToVectorCases(
+ vectorU32Range(4),
+ sparseU32Range(),
+ u32_remainder_const
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_arithmetic.spec.ts
index 88667e8233..6df16f303c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_arithmetic.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_arithmetic.spec.ts
@@ -4,307 +4,14 @@ Execution Tests for the u32 arithmetic binary expression operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeU32, TypeVec } from '../../../../util/conversion.js';
-import { sparseU32Range, vectorU32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import {
- allInputSources,
- generateBinaryToU32Cases,
- generateU32VectorBinaryToVectorCases,
- generateVectorU32BinaryToVectorCases,
- run,
-} from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { allInputSources, run } from '../expression.js';
import { binary, compoundBinary } from './binary.js';
-
-function u32_add(x: number, y: number): number | undefined {
- return x + y;
-}
-
-function u32_subtract(x: number, y: number): number | undefined {
- return x - y;
-}
-
-function u32_multiply(x: number, y: number): number | undefined {
- return Math.imul(x, y);
-}
-
-function u32_divide_non_const(x: number, y: number): number | undefined {
- if (y === 0) {
- return x;
- }
- return x / y;
-}
-
-function u32_divide_const(x: number, y: number): number | undefined {
- if (y === 0) {
- return undefined;
- }
- return x / y;
-}
-
-function u32_remainder_non_const(x: number, y: number): number | undefined {
- if (y === 0) {
- return 0;
- }
- return x % y;
-}
-
-function u32_remainder_const(x: number, y: number): number | undefined {
- if (y === 0) {
- return undefined;
- }
- return x % y;
-}
+import { d } from './u32_arithmetic.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('binary/u32_arithmetic', {
- addition: () => {
- return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_add);
- },
- subtraction: () => {
- return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_subtract);
- },
- multiplication: () => {
- return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_multiply);
- },
- division_non_const: () => {
- return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_divide_non_const);
- },
- division_const: () => {
- return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_divide_const);
- },
- remainder_non_const: () => {
- return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_remainder_non_const);
- },
- remainder_const: () => {
- return generateBinaryToU32Cases(sparseU32Range(), sparseU32Range(), u32_remainder_const);
- },
- addition_scalar_vector2: () => {
- return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(2), u32_add);
- },
- addition_scalar_vector3: () => {
- return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(3), u32_add);
- },
- addition_scalar_vector4: () => {
- return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(4), u32_add);
- },
- addition_vector2_scalar: () => {
- return generateVectorU32BinaryToVectorCases(vectorU32Range(2), sparseU32Range(), u32_add);
- },
- addition_vector3_scalar: () => {
- return generateVectorU32BinaryToVectorCases(vectorU32Range(3), sparseU32Range(), u32_add);
- },
- addition_vector4_scalar: () => {
- return generateVectorU32BinaryToVectorCases(vectorU32Range(4), sparseU32Range(), u32_add);
- },
- subtraction_scalar_vector2: () => {
- return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(2), u32_subtract);
- },
- subtraction_scalar_vector3: () => {
- return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(3), u32_subtract);
- },
- subtraction_scalar_vector4: () => {
- return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(4), u32_subtract);
- },
- subtraction_vector2_scalar: () => {
- return generateVectorU32BinaryToVectorCases(vectorU32Range(2), sparseU32Range(), u32_subtract);
- },
- subtraction_vector3_scalar: () => {
- return generateVectorU32BinaryToVectorCases(vectorU32Range(3), sparseU32Range(), u32_subtract);
- },
- subtraction_vector4_scalar: () => {
- return generateVectorU32BinaryToVectorCases(vectorU32Range(4), sparseU32Range(), u32_subtract);
- },
- multiplication_scalar_vector2: () => {
- return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(2), u32_multiply);
- },
- multiplication_scalar_vector3: () => {
- return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(3), u32_multiply);
- },
- multiplication_scalar_vector4: () => {
- return generateU32VectorBinaryToVectorCases(sparseU32Range(), vectorU32Range(4), u32_multiply);
- },
- multiplication_vector2_scalar: () => {
- return generateVectorU32BinaryToVectorCases(vectorU32Range(2), sparseU32Range(), u32_multiply);
- },
- multiplication_vector3_scalar: () => {
- return generateVectorU32BinaryToVectorCases(vectorU32Range(3), sparseU32Range(), u32_multiply);
- },
- multiplication_vector4_scalar: () => {
- return generateVectorU32BinaryToVectorCases(vectorU32Range(4), sparseU32Range(), u32_multiply);
- },
- division_scalar_vector2_non_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(2),
- u32_divide_non_const
- );
- },
- division_scalar_vector3_non_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(3),
- u32_divide_non_const
- );
- },
- division_scalar_vector4_non_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(4),
- u32_divide_non_const
- );
- },
- division_vector2_scalar_non_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(2),
- sparseU32Range(),
- u32_divide_non_const
- );
- },
- division_vector3_scalar_non_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(3),
- sparseU32Range(),
- u32_divide_non_const
- );
- },
- division_vector4_scalar_non_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(4),
- sparseU32Range(),
- u32_divide_non_const
- );
- },
- division_scalar_vector2_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(2),
- u32_divide_const
- );
- },
- division_scalar_vector3_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(3),
- u32_divide_const
- );
- },
- division_scalar_vector4_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(4),
- u32_divide_const
- );
- },
- division_vector2_scalar_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(2),
- sparseU32Range(),
- u32_divide_const
- );
- },
- division_vector3_scalar_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(3),
- sparseU32Range(),
- u32_divide_const
- );
- },
- division_vector4_scalar_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(4),
- sparseU32Range(),
- u32_divide_const
- );
- },
- remainder_scalar_vector2_non_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(2),
- u32_remainder_non_const
- );
- },
- remainder_scalar_vector3_non_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(3),
- u32_remainder_non_const
- );
- },
- remainder_scalar_vector4_non_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(4),
- u32_remainder_non_const
- );
- },
- remainder_vector2_scalar_non_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(2),
- sparseU32Range(),
- u32_remainder_non_const
- );
- },
- remainder_vector3_scalar_non_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(3),
- sparseU32Range(),
- u32_remainder_non_const
- );
- },
- remainder_vector4_scalar_non_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(4),
- sparseU32Range(),
- u32_remainder_non_const
- );
- },
- remainder_scalar_vector2_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(2),
- u32_remainder_const
- );
- },
- remainder_scalar_vector3_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(3),
- u32_remainder_const
- );
- },
- remainder_scalar_vector4_const: () => {
- return generateU32VectorBinaryToVectorCases(
- sparseU32Range(),
- vectorU32Range(4),
- u32_remainder_const
- );
- },
- remainder_vector2_scalar_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(2),
- sparseU32Range(),
- u32_remainder_const
- );
- },
- remainder_vector3_scalar_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(3),
- sparseU32Range(),
- u32_remainder_const
- );
- },
- remainder_vector4_scalar_const: () => {
- return generateVectorU32BinaryToVectorCases(
- vectorU32Range(4),
- sparseU32Range(),
- u32_remainder_const
- );
- },
-});
-
g.test('addition')
.specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
.desc(
@@ -317,7 +24,7 @@ Expression: x + y
)
.fn(async t => {
const cases = await d.get('addition');
- await run(t, binary('+'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, binary('+'), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('addition_compound')
@@ -332,7 +39,7 @@ Expression: x += y
)
.fn(async t => {
const cases = await d.get('addition');
- await run(t, compoundBinary('+='), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, compoundBinary('+='), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('subtraction')
@@ -347,7 +54,7 @@ Expression: x - y
)
.fn(async t => {
const cases = await d.get('subtraction');
- await run(t, binary('-'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, binary('-'), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('subtraction_compound')
@@ -362,7 +69,7 @@ Expression: x -= y
)
.fn(async t => {
const cases = await d.get('subtraction');
- await run(t, compoundBinary('-='), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, compoundBinary('-='), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('multiplication')
@@ -377,7 +84,7 @@ Expression: x * y
)
.fn(async t => {
const cases = await d.get('multiplication');
- await run(t, binary('*'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, binary('*'), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('multiplication_compound')
@@ -392,7 +99,7 @@ Expression: x *= y
)
.fn(async t => {
const cases = await d.get('multiplication');
- await run(t, compoundBinary('*='), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, compoundBinary('*='), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('division')
@@ -409,7 +116,7 @@ Expression: x / y
const cases = await d.get(
t.params.inputSource === 'const' ? 'division_const' : 'division_non_const'
);
- await run(t, binary('/'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, binary('/'), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('division_compound')
@@ -426,7 +133,7 @@ Expression: x /= y
const cases = await d.get(
t.params.inputSource === 'const' ? 'division_const' : 'division_non_const'
);
- await run(t, compoundBinary('/='), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, compoundBinary('/='), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('remainder')
@@ -443,7 +150,7 @@ Expression: x % y
const cases = await d.get(
t.params.inputSource === 'const' ? 'remainder_const' : 'remainder_non_const'
);
- await run(t, binary('%'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, binary('%'), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('remainder_compound')
@@ -460,7 +167,7 @@ Expression: x %= y
const cases = await d.get(
t.params.inputSource === 'const' ? 'remainder_const' : 'remainder_non_const'
);
- await run(t, compoundBinary('%='), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, compoundBinary('%='), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('addition_scalar_vector')
@@ -475,9 +182,9 @@ Expression: x + y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const cases = await d.get(`addition_scalar_vector${vec_size}`);
- await run(t, binary('+'), [TypeU32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('+'), [Type.u32, vec_type], vec_type, t.params, cases);
});
g.test('addition_vector_scalar')
@@ -492,9 +199,9 @@ Expression: x + y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const cases = await d.get(`addition_vector${vec_size}_scalar`);
- await run(t, binary('+'), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, binary('+'), [vec_type, Type.u32], vec_type, t.params, cases);
});
g.test('addition_vector_scalar_compound')
@@ -509,9 +216,9 @@ Expression: x += y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const cases = await d.get(`addition_vector${vec_size}_scalar`);
- await run(t, compoundBinary('+='), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, compoundBinary('+='), [vec_type, Type.u32], vec_type, t.params, cases);
});
g.test('subtraction_scalar_vector')
@@ -526,9 +233,9 @@ Expression: x - y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const cases = await d.get(`subtraction_scalar_vector${vec_size}`);
- await run(t, binary('-'), [TypeU32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('-'), [Type.u32, vec_type], vec_type, t.params, cases);
});
g.test('subtraction_vector_scalar')
@@ -543,9 +250,9 @@ Expression: x - y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const cases = await d.get(`subtraction_vector${vec_size}_scalar`);
- await run(t, binary('-'), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, binary('-'), [vec_type, Type.u32], vec_type, t.params, cases);
});
g.test('subtraction_vector_scalar_compound')
@@ -560,9 +267,9 @@ Expression: x -= y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const cases = await d.get(`subtraction_vector${vec_size}_scalar`);
- await run(t, compoundBinary('-='), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, compoundBinary('-='), [vec_type, Type.u32], vec_type, t.params, cases);
});
g.test('multiplication_scalar_vector')
@@ -577,9 +284,9 @@ Expression: x * y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const cases = await d.get(`multiplication_scalar_vector${vec_size}`);
- await run(t, binary('*'), [TypeU32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('*'), [Type.u32, vec_type], vec_type, t.params, cases);
});
g.test('multiplication_vector_scalar')
@@ -594,9 +301,9 @@ Expression: x * y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const cases = await d.get(`multiplication_vector${vec_size}_scalar`);
- await run(t, binary('*'), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, binary('*'), [vec_type, Type.u32], vec_type, t.params, cases);
});
g.test('multiplication_vector_scalar_compound')
@@ -611,9 +318,9 @@ Expression: x *= y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const cases = await d.get(`multiplication_vector${vec_size}_scalar`);
- await run(t, compoundBinary('*='), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, compoundBinary('*='), [vec_type, Type.u32], vec_type, t.params, cases);
});
g.test('division_scalar_vector')
@@ -628,10 +335,10 @@ Expression: x / y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`division_scalar_vector${vec_size}_${source}`);
- await run(t, binary('/'), [TypeU32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('/'), [Type.u32, vec_type], vec_type, t.params, cases);
});
g.test('division_vector_scalar')
@@ -646,10 +353,10 @@ Expression: x / y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`division_vector${vec_size}_scalar_${source}`);
- await run(t, binary('/'), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, binary('/'), [vec_type, Type.u32], vec_type, t.params, cases);
});
g.test('division_vector_scalar_compound')
@@ -664,10 +371,10 @@ Expression: x /= y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`division_vector${vec_size}_scalar_${source}`);
- await run(t, compoundBinary('/='), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, compoundBinary('/='), [vec_type, Type.u32], vec_type, t.params, cases);
});
g.test('remainder_scalar_vector')
@@ -682,10 +389,10 @@ Expression: x % y
)
.fn(async t => {
const vec_size = t.params.vectorize_rhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`remainder_scalar_vector${vec_size}_${source}`);
- await run(t, binary('%'), [TypeU32, vec_type], vec_type, t.params, cases);
+ await run(t, binary('%'), [Type.u32, vec_type], vec_type, t.params, cases);
});
g.test('remainder_vector_scalar')
@@ -700,10 +407,10 @@ Expression: x % y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`remainder_vector${vec_size}_scalar_${source}`);
- await run(t, binary('%'), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, binary('%'), [vec_type, Type.u32], vec_type, t.params, cases);
});
g.test('remainder_vector_scalar_compound')
@@ -718,8 +425,8 @@ Expression: x %= y
)
.fn(async t => {
const vec_size = t.params.vectorize_lhs;
- const vec_type = TypeVec(vec_size, TypeU32);
+ const vec_type = Type.vec(vec_size, Type.u32);
const source = t.params.inputSource === 'const' ? 'const' : 'non_const';
const cases = await d.get(`remainder_vector${vec_size}_scalar_${source}`);
- await run(t, compoundBinary('%='), [vec_type, TypeU32], vec_type, t.params, cases);
+ await run(t, compoundBinary('%='), [vec_type, Type.u32], vec_type, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_comparison.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_comparison.cache.ts
new file mode 100644
index 0000000000..93b0500496
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_comparison.cache.ts
@@ -0,0 +1,21 @@
+import { bool, u32 } from '../../../../util/conversion.js';
+import { vectorU32Range } from '../../../../util/math.js';
+import { Case } from '../case.js';
+import { makeCaseCache } from '../case_cache.js';
+
+/**
+ * @returns a test case for the provided left hand & right hand values and
+ * expected boolean result.
+ */
+function makeCase(lhs: number, rhs: number, expected_answer: boolean): Case {
+ return { input: [u32(lhs), u32(rhs)], expected: bool(expected_answer) };
+}
+
+export const d = makeCaseCache('binary/u32_comparison', {
+ equals: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] === v[1])),
+ not_equals: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] !== v[1])),
+ less_than: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] < v[1])),
+ less_equal: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] <= v[1])),
+ greater_than: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] > v[1])),
+ greater_equal: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] >= v[1])),
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_comparison.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_comparison.spec.ts
index 1f693da5fd..5bb6767b01 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_comparison.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/binary/u32_comparison.spec.ts
@@ -4,32 +4,14 @@ Execution Tests for the u32 comparison expressions
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { u32, bool, TypeBool, TypeU32 } from '../../../../util/conversion.js';
-import { vectorU32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, Case, run } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { allInputSources, run } from '../expression.js';
import { binary } from './binary.js';
+import { d } from './u32_comparison.cache.js';
export const g = makeTestGroup(GPUTest);
-/**
- * @returns a test case for the provided left hand & right hand values and
- * expected boolean result.
- */
-function makeCase(lhs: number, rhs: number, expected_answer: boolean): Case {
- return { input: [u32(lhs), u32(rhs)], expected: bool(expected_answer) };
-}
-
-export const d = makeCaseCache('binary/u32_comparison', {
- equals: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] === v[1])),
- not_equals: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] !== v[1])),
- less_than: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] < v[1])),
- less_equal: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] <= v[1])),
- greater_than: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] > v[1])),
- greater_equal: () => vectorU32Range(2).map(v => makeCase(v[0], v[1], v[0] >= v[1])),
-});
-
g.test('equals')
.specURL('https://www.w3.org/TR/WGSL/#comparison-expr')
.desc(
@@ -42,7 +24,7 @@ Expression: x == y
)
.fn(async t => {
const cases = await d.get('equals');
- await run(t, binary('=='), [TypeU32, TypeU32], TypeBool, t.params, cases);
+ await run(t, binary('=='), [Type.u32, Type.u32], Type.bool, t.params, cases);
});
g.test('not_equals')
@@ -57,7 +39,7 @@ Expression: x != y
)
.fn(async t => {
const cases = await d.get('not_equals');
- await run(t, binary('!='), [TypeU32, TypeU32], TypeBool, t.params, cases);
+ await run(t, binary('!='), [Type.u32, Type.u32], Type.bool, t.params, cases);
});
g.test('less_than')
@@ -72,7 +54,7 @@ Expression: x < y
)
.fn(async t => {
const cases = await d.get('less_than');
- await run(t, binary('<'), [TypeU32, TypeU32], TypeBool, t.params, cases);
+ await run(t, binary('<'), [Type.u32, Type.u32], Type.bool, t.params, cases);
});
g.test('less_equals')
@@ -87,7 +69,7 @@ Expression: x <= y
)
.fn(async t => {
const cases = await d.get('less_equal');
- await run(t, binary('<='), [TypeU32, TypeU32], TypeBool, t.params, cases);
+ await run(t, binary('<='), [Type.u32, Type.u32], Type.bool, t.params, cases);
});
g.test('greater_than')
@@ -102,7 +84,7 @@ Expression: x > y
)
.fn(async t => {
const cases = await d.get('greater_than');
- await run(t, binary('>'), [TypeU32, TypeU32], TypeBool, t.params, cases);
+ await run(t, binary('>'), [Type.u32, Type.u32], Type.bool, t.params, cases);
});
g.test('greater_equals')
@@ -117,5 +99,5 @@ Expression: x >= y
)
.fn(async t => {
const cases = await d.get('greater_equal');
- await run(t, binary('>='), [TypeU32, TypeU32], TypeBool, t.params, cases);
+ await run(t, binary('>='), [Type.u32, Type.u32], Type.bool, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.cache.ts
new file mode 100644
index 0000000000..9634ae8f34
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.cache.ts
@@ -0,0 +1,26 @@
+import { abstractInt } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { absBigInt, fullI64Range } from '../../../../../util/math.js';
+import { CaseListBuilder, makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract_float|abstract_int]
+const cases: Record<string, CaseListBuilder> = {
+ ...(['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait === 'abstract' ? 'abstract_float' : trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ 'unfiltered',
+ FP[trait].absInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {}),
+ abstract_int: () => {
+ return fullI64Range().map(e => {
+ return { input: abstractInt(e), expected: abstractInt(absBigInt(e)) };
+ });
+ },
+};
+
+export const d = makeCaseCache('abs', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts
index 05d5242f73..75d41ab163 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/abs.spec.ts
@@ -1,14 +1,14 @@
export const description = `
Execution tests for the 'abs' builtin function
-S is AbstractInt, i32, or u32
+S is abstract-int, i32, or u32
T is S or vecN<S>
@const fn abs(e: T ) -> T
The absolute value of e. Component-wise when T is a vector. If e is a signed
integral scalar type and evaluates to the largest negative value, then the
result is e. If e is an unsigned integral type, then the result is e.
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn abs(e: T ) -> T
Returns the absolute value of e (e.g. e with a positive sign bit).
@@ -18,47 +18,26 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
import { kBit } from '../../../../../util/constants.js';
-import {
- i32Bits,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeU32,
- u32Bits,
- TypeAbstractFloat,
-} from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range, fullF64Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type, i32Bits, u32Bits } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { d } from './abs.cache.js';
+import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('abs', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.absInterval);
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.absInterval);
- },
- abstract: () => {
- return FP.abstract.generateScalarToIntervalCases(
- fullF64Range(),
- 'unfiltered',
- FP.abstract.absInterval
- );
- },
-});
-
g.test('abstract_int')
.specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
.desc(`abstract int tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_int');
+ await run(t, abstractIntBuiltin('abs'), [Type.abstractInt], Type.abstractInt, t.params, cases);
+ });
g.test('u32')
.specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
@@ -67,7 +46,7 @@ g.test('u32')
u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
- await run(t, builtin('abs'), [TypeU32], TypeU32, t.params, [
+ await run(t, builtin('abs'), [Type.u32], Type.u32, t.params, [
// Min and Max u32
{ input: u32Bits(kBit.u32.min), expected: u32Bits(kBit.u32.min) },
{ input: u32Bits(kBit.u32.max), expected: u32Bits(kBit.u32.max) },
@@ -114,7 +93,7 @@ g.test('i32')
u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
- await run(t, builtin('abs'), [TypeI32], TypeI32, t.params, [
+ await run(t, builtin('abs'), [Type.i32], Type.i32, t.params, [
// Min and max i32
// If e evaluates to the largest negative value, then the result is e.
{ input: i32Bits(kBit.i32.negative.min), expected: i32Bits(kBit.i32.negative.min) },
@@ -166,8 +145,15 @@ g.test('abstract_float')
.combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
- const cases = await d.get('abstract');
- await run(t, abstractBuiltin('abs'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+ const cases = await d.get('abstract_float');
+ await run(
+ t,
+ abstractFloatBuiltin('abs'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
});
g.test('f32')
@@ -178,7 +164,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('abs'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('abs'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -192,5 +178,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('abs'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('abs'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.cache.ts
new file mode 100644
index 0000000000..466618b6db
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.cache.ts
@@ -0,0 +1,24 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ [...linearRange(-1, 1, 100), ...FP[trait].scalarRange()], // acos is defined on [-1, 1]
+ nonConst ? 'unfiltered' : 'finite',
+ // acos has an ulp or inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].acosInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('acos', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts
index 5755c07905..46089d8576 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acos.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'acos' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn acos(e: T ) -> T
Returns the arc cosine of e. Component-wise when T is a vector.
@@ -9,48 +9,33 @@ Returns the arc cosine of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { linearRange, fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { d } from './acos.cache.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
export const g = makeTestGroup(GPUTest);
-const f32_inputs = [
- ...linearRange(-1, 1, 100), // acos is defined on [-1, 1]
- ...fullF32Range(),
-];
-
-const f16_inputs = [
- ...linearRange(-1, 1, 100), // acos is defined on [-1, 1]
- ...fullF16Range(),
-];
-
-export const d = makeCaseCache('acos', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.acosInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.acosInterval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.acosInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.acosInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('acos'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -60,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('acos'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('acos'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -74,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('acos'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('acos'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.cache.ts
new file mode 100644
index 0000000000..587131140d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.cache.ts
@@ -0,0 +1,24 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ [...biasedRange(1, 2, 100), ...FP[trait].scalarRange()], // x near 1 can be problematic to implement
+ nonConst ? 'unfiltered' : 'finite',
+ // acosh has an inherited accuracy, so is only expected to be as accurate as f32
+ ...FP[trait !== 'abstract' ? trait : 'f32'].acoshIntervals
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('acosh', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts
index cc78ce3eee..531a2479c6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/acosh.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'acosh' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn acosh(e: T ) -> T
Returns the hyperbolic arc cosine of e. The result is 0 when e < 1.
@@ -13,47 +13,33 @@ Note: The result is not mathematically meaningful when e < 1.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { biasedRange, fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { d } from './acosh.cache.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
export const g = makeTestGroup(GPUTest);
-const f32_inputs = [
- ...biasedRange(1, 2, 100), // x near 1 can be problematic to implement
- ...fullF32Range(),
-];
-const f16_inputs = [
- ...biasedRange(1, 2, 100), // x near 1 can be problematic to implement
- ...fullF16Range(),
-];
-
-export const d = makeCaseCache('acosh', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', ...FP.f32.acoshIntervals);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', ...FP.f32.acoshIntervals);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', ...FP.f16.acoshIntervals);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', ...FP.f16.acoshIntervals);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('acosh'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -63,7 +49,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('acosh'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('acosh'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -77,5 +63,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('acosh'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('acosh'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts
index 9a2938c1d5..74e072703d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/all.spec.ts
@@ -10,15 +10,7 @@ Returns true if each component of e is true if e is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import {
- False,
- True,
- TypeBool,
- TypeVec,
- vec2,
- vec3,
- vec4,
-} from '../../../../../util/conversion.js';
+import { False, True, Type, vec2, vec3, vec4 } from '../../../../../util/conversion.js';
import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -36,14 +28,14 @@ g.test('bool')
.fn(async t => {
const overloads = {
scalar: {
- type: TypeBool,
+ type: Type.bool,
cases: [
{ input: False, expected: False },
{ input: True, expected: True },
],
},
vec2: {
- type: TypeVec(2, TypeBool),
+ type: Type.vec(2, Type.bool),
cases: [
{ input: vec2(False, False), expected: False },
{ input: vec2(True, False), expected: False },
@@ -52,7 +44,7 @@ g.test('bool')
],
},
vec3: {
- type: TypeVec(3, TypeBool),
+ type: Type.vec(3, Type.bool),
cases: [
{ input: vec3(False, False, False), expected: False },
{ input: vec3(True, False, False), expected: False },
@@ -65,7 +57,7 @@ g.test('bool')
],
},
vec4: {
- type: TypeVec(4, TypeBool),
+ type: Type.vec(4, Type.bool),
cases: [
{ input: vec4(False, False, False, False), expected: False },
{ input: vec4(False, True, False, False), expected: False },
@@ -88,5 +80,5 @@ g.test('bool')
};
const overload = overloads[t.params.overload];
- await run(t, builtin('all'), [overload.type], TypeBool, t.params, overload.cases);
+ await run(t, builtin('all'), [overload.type], Type.bool, t.params, overload.cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts
index 19ed7d186f..43c599e2aa 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/any.spec.ts
@@ -10,15 +10,7 @@ Returns true if any component of e is true if e is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import {
- False,
- True,
- TypeBool,
- TypeVec,
- vec2,
- vec3,
- vec4,
-} from '../../../../../util/conversion.js';
+import { False, True, Type, vec2, vec3, vec4 } from '../../../../../util/conversion.js';
import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -36,14 +28,14 @@ g.test('bool')
.fn(async t => {
const overloads = {
scalar: {
- type: TypeBool,
+ type: Type.bool,
cases: [
{ input: False, expected: False },
{ input: True, expected: True },
],
},
vec2: {
- type: TypeVec(2, TypeBool),
+ type: Type.vec(2, Type.bool),
cases: [
{ input: vec2(False, False), expected: False },
{ input: vec2(True, False), expected: True },
@@ -52,7 +44,7 @@ g.test('bool')
],
},
vec3: {
- type: TypeVec(3, TypeBool),
+ type: Type.vec(3, Type.bool),
cases: [
{ input: vec3(False, False, False), expected: False },
{ input: vec3(True, False, False), expected: True },
@@ -65,7 +57,7 @@ g.test('bool')
],
},
vec4: {
- type: TypeVec(4, TypeBool),
+ type: Type.vec(4, Type.bool),
cases: [
{ input: vec4(False, False, False, False), expected: False },
{ input: vec4(False, True, False, False), expected: True },
@@ -88,5 +80,5 @@ g.test('bool')
};
const overload = overloads[t.params.overload];
- await run(t, builtin('any'), [overload.type], TypeBool, t.params, overload.cases);
+ await run(t, builtin('any'), [overload.type], Type.bool, t.params, overload.cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.cache.ts
new file mode 100644
index 0000000000..d9e6280e0d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.cache.ts
@@ -0,0 +1,24 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ [...linearRange(-1, 1, 100), ...FP[trait].scalarRange()], // asin is defined on [-1, 1]
+ nonConst ? 'unfiltered' : 'finite',
+ // asin has an ulp or inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].asinInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('asin', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts
index 8d18ebb303..f368c3838c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asin.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'asin' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn asin(e: T ) -> T
Returns the arc sine of e. Component-wise when T is a vector.
@@ -9,48 +9,33 @@ Returns the arc sine of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { linearRange, fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { d } from './asin.cache.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
export const g = makeTestGroup(GPUTest);
-const f32_inputs = [
- ...linearRange(-1, 1, 100), // asin is defined on [-1, 1]
- ...fullF32Range(),
-];
-
-const f16_inputs = [
- ...linearRange(-1, 1, 100), // asin is defined on [-1, 1]
- ...fullF16Range(),
-];
-
-export const d = makeCaseCache('asin', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.asinInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.asinInterval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.asinInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.asinInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('asin'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -60,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('asin'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('asin'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -74,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('asin'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('asin'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.cache.ts
new file mode 100644
index 0000000000..4ee66f1ea6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.cache.ts
@@ -0,0 +1,18 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ 'unfiltered',
+ // asinh has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].asinhInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('asinh', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts
index 9a8384e090..60867a9bce 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/asinh.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'sinh' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn asinh(e: T ) -> T
Returns the hyperbolic arc sine of e.
@@ -12,32 +12,33 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { d } from './asinh.cache.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('asinh', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.asinhInterval);
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.asinhInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float test`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('asinh'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -47,7 +48,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('asinh'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('asinh'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -61,5 +62,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('asinh'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('asinh'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.cache.ts
new file mode 100644
index 0000000000..e39e40448e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.cache.ts
@@ -0,0 +1,25 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+const known_values = [-Math.sqrt(3), -1, -1 / Math.sqrt(3), 0, 1, 1 / Math.sqrt(3), Math.sqrt(3)];
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ [...known_values, ...FP[trait].scalarRange()],
+ nonConst ? 'unfiltered' : 'finite',
+ // atan has an ulp accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].atanInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('atan', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts
index 3d0d3e6725..824a7cd0b5 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'atan' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn atan(e: T ) -> T
Returns the arc tangent of e. Component-wise when T is a vector.
@@ -10,43 +10,33 @@ Returns the arc tangent of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { d } from './atan.cache.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
export const g = makeTestGroup(GPUTest);
-const known_values = [-Math.sqrt(3), -1, -1 / Math.sqrt(3), 0, 1, 1 / Math.sqrt(3), Math.sqrt(3)];
-
-const f32_inputs = [...known_values, ...fullF32Range()];
-const f16_inputs = [...known_values, ...fullF16Range()];
-
-export const d = makeCaseCache('atan', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.atanInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.atanInterval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.atanInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.atanInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('atan'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -62,7 +52,7 @@ TODO(#792): Decide what the ground-truth is for these tests. [1]
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('atan'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('atan'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -76,5 +66,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('atan'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('atan'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.cache.ts
new file mode 100644
index 0000000000..be25c2dffb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.cache.ts
@@ -0,0 +1,35 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ // Using sparse range since there are N^2 cases being generated, and also including extra values
+ // around 0, where there is a discontinuity that implementations may behave badly at.
+ const numeric_range = [
+ ...FP[trait].sparseScalarRange(),
+ ...linearRange(
+ FP[trait].constants().negative.max,
+ FP[trait].constants().positive.min,
+ 10
+ ),
+ ];
+ return FP[trait].generateScalarPairToIntervalCases(
+ numeric_range,
+ numeric_range,
+ nonConst ? 'unfiltered' : 'finite',
+ // atan2 has an ulp accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].atan2Interval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('atan2', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts
index fbace73dd2..067d1fdc66 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atan2.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'atan2' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn atan2(e1: T ,e2: T ) -> T
Returns the arc tangent of e1 over e2. Component-wise when T is a vector.
@@ -9,47 +9,33 @@ Returns the arc tangent of e1 over e2. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { linearRange, sparseF32Range, sparseF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { d } from './atan2.cache.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
export const g = makeTestGroup(GPUTest);
-const cases = (['f32', 'f16'] as const)
- .flatMap(kind =>
- ([true, false] as const).map(nonConst => ({
- [`${kind}_${nonConst ? 'non_const' : 'const'}`]: () => {
- const fp = FP[kind];
- // Using sparse range since there are N^2 cases being generated, and also including extra values
- // around 0, where there is a discontinuity that implementations may behave badly at.
- const numeric_range = [
- ...(kind === 'f32' ? sparseF32Range() : sparseF16Range()),
- ...linearRange(fp.constants().negative.max, fp.constants().positive.min, 10),
- ];
- return fp.generateScalarPairToIntervalCases(
- numeric_range,
- numeric_range,
- nonConst ? 'unfiltered' : 'finite',
- fp.atan2Interval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('atan2', cases);
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get(`abstract_const`);
+ await run(
+ t,
+ abstractFloatBuiltin('atan2'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -65,7 +51,7 @@ TODO(#792): Decide what the ground-truth is for these tests. [1]
)
.fn(async t => {
const cases = await d.get(`f32_${t.params.inputSource === 'const' ? 'const' : 'non_const'}`);
- await run(t, builtin('atan2'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('atan2'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -79,5 +65,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(`f16_${t.params.inputSource === 'const' ? 'const' : 'non_const'}`);
- await run(t, builtin('atan2'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('atan2'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.cache.ts
new file mode 100644
index 0000000000..bcd000bf61
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.cache.ts
@@ -0,0 +1,32 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ [
+ // discontinuity at x = -1
+ ...biasedRange(FP[trait].constants().negative.less_than_one, -0.9, 20),
+ -1,
+ // discontinuity at x = 1
+ ...biasedRange(FP[trait].constants().positive.less_than_one, 0.9, 20),
+ 1,
+ ...FP[trait].scalarRange(),
+ ],
+ nonConst ? 'unfiltered' : 'finite',
+ // atanh has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].atanhInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('atanh', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts
index 90f322a7ea..644efafd2f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atanh.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'atanh' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn atanh(e: T ) -> T
Returns the hyperbolic arc tangent of e. The result is 0 when abs(e) ≥ 1.
@@ -12,54 +12,33 @@ Note: The result is not mathematically meaningful when abs(e) >= 1.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { kValue } from '../../../../../util/constants.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { biasedRange, fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { d } from './atanh.cache.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
export const g = makeTestGroup(GPUTest);
-const f32_inputs = [
- ...biasedRange(kValue.f32.negative.less_than_one, -0.9, 20), // discontinuity at x = -1
- -1,
- ...biasedRange(kValue.f32.positive.less_than_one, 0.9, 20), // discontinuity at x = 1
- 1,
- ...fullF32Range(),
-];
-const f16_inputs = [
- ...biasedRange(kValue.f16.negative.less_than_one, -0.9, 20), // discontinuity at x = -1
- -1,
- ...biasedRange(kValue.f16.positive.less_than_one, 0.9, 20), // discontinuity at x = 1
- 1,
- ...fullF16Range(),
-];
-
-export const d = makeCaseCache('atanh', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.atanhInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.atanhInterval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.atanhInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.atanhInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('atanh'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -69,7 +48,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('atanh'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('atanh'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -83,5 +62,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('atanh'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('atanh'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts
index 37d3ce5292..187a555449 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAdd.spec.ts
@@ -35,7 +35,7 @@ fn atomicAdd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
u
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -72,7 +72,7 @@ fn atomicAdd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
u
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
// Allocate one extra element to ensure it doesn't get modified
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts
index ed5cfa84a3..ad05bd851d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicAnd.spec.ts
@@ -38,7 +38,7 @@ fn atomicAnd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -91,7 +91,7 @@ fn atomicAnd(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts
index 2556bb744b..79e0597af6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicCompareExchangeWeak.spec.ts
@@ -48,7 +48,7 @@ struct __atomic_compare_exchange_result<T> {
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(async t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -187,7 +187,7 @@ struct __atomic_compare_exchange_result<T> {
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(async t => {
const numInvocations = t.params.workgroupSize;
@@ -333,12 +333,17 @@ struct __atomic_compare_exchange_result<T> {
.params(u =>
u
.combine('workgroupSize', onlyWorkgroupSizes) //
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(async t => {
const numInvocations = t.params.workgroupSize;
const scalarType = t.params.scalarType;
+ t.skipIf(
+ numInvocations > t.device.limits.maxComputeWorkgroupSizeX,
+ `${numInvocations} > maxComputeWorkgroupSizeX(${t.device.limits.maxComputeWorkgroupSizeX})`
+ );
+
// Number of times each workgroup attempts to exchange the same value to the same memory address
const numWrites = 4;
@@ -550,12 +555,17 @@ struct __atomic_compare_exchange_result<T> {
.params(u =>
u
.combine('workgroupSize', onlyWorkgroupSizes) //
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(async t => {
const numInvocations = t.params.workgroupSize;
const scalarType = t.params.scalarType;
+ t.skipIf(
+ numInvocations > t.device.limits.maxComputeWorkgroupSizeX,
+ `${numInvocations} > maxComputeWorkgroupSizeX(${t.device.limits.maxComputeWorkgroupSizeX})`
+ );
+
// Number of times each workgroup attempts to exchange the same value to the same memory address
const numWrites = 4;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts
index 540ac16b07..00b6ddb7e3 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicExchange.spec.ts
@@ -26,7 +26,7 @@ fn atomicExchange(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -125,7 +125,7 @@ fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize;
@@ -236,7 +236,7 @@ fn atomicExchange(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(async t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -350,7 +350,7 @@ fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(async t => {
const numInvocations = t.params.workgroupSize;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts
index 2aac7bb9b9..23ca127cae 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicLoad.spec.ts
@@ -26,7 +26,7 @@ fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -117,7 +117,7 @@ fn atomicLoad(atomic_ptr: ptr<AS, atomic<T>, read_write>) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts
index 066d673018..86bb8b460d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMax.spec.ts
@@ -35,7 +35,7 @@ fn atomicMax(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
u
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -72,7 +72,7 @@ fn atomicMax(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
u
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
// Allocate one extra element to ensure it doesn't get modified
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts
index ad880c4182..d9a42d15ee 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicMin.spec.ts
@@ -35,7 +35,7 @@ fn atomicMin(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
u
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
// Allocate one extra element to ensure it doesn't get modified
@@ -71,7 +71,7 @@ fn atomicMin(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
u
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
// Allocate one extra element to ensure it doesn't get modified
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts
index 3892d41b38..8ddba24385 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicOr.spec.ts
@@ -38,7 +38,7 @@ fn atomicOr(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -90,7 +90,7 @@ fn atomicOr(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts
index 18ff72975d..4d226e312b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicStore.spec.ts
@@ -32,7 +32,7 @@ fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T)
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -72,7 +72,7 @@ fn atomicStore(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T)
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize;
@@ -117,7 +117,7 @@ one of the values written.
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(async t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -210,7 +210,7 @@ one of the values written.
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(async t => {
const numInvocations = t.params.workgroupSize;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts
index 6cea190299..8fdced1df2 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicSub.spec.ts
@@ -35,7 +35,7 @@ fn atomicSub(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
u
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -72,7 +72,7 @@ fn atomicSub(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
u
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
// Allocate one extra element to ensure it doesn't get modified
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts
index 99192fd9fe..2240043590 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/atomicXor.spec.ts
@@ -38,7 +38,7 @@ fn atomicXor(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize * t.params.dispatchSize;
@@ -91,7 +91,7 @@ fn atomicXor(atomic_ptr: ptr<AS, atomic<T>, read_write>, v: T) -> T
.combine('workgroupSize', workgroupSizes)
.combine('dispatchSize', dispatchSizes)
.combine('mapId', keysOf(kMapId))
- .combine('scalarType', ['u32', 'i32'])
+ .combine('scalarType', ['u32', 'i32'] as const)
)
.fn(t => {
const numInvocations = t.params.workgroupSize;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts
index ed02467f80..e12cf537cd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/atomics/harness.ts
@@ -25,15 +25,14 @@ export const kMapId = {
},
};
-export function typedArrayCtor(scalarType: string): TypedArrayBufferViewConstructor {
+export function typedArrayCtor(
+ scalarType: 'u32' | 'i32'
+): TypedArrayBufferViewConstructor<Uint32Array | Int32Array> {
switch (scalarType) {
case 'u32':
return Uint32Array;
case 'i32':
return Int32Array;
- default:
- assert(false, 'Atomic variables can only by u32 or i32');
- return Uint8Array;
}
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.cache.ts
new file mode 100644
index 0000000000..75dfea0086
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.cache.ts
@@ -0,0 +1,837 @@
+import { assert } from '../../../../../../common/util/util.js';
+import { Comparator, alwaysPass, anyOf } from '../../../../../util/compare.js';
+import { kBit, kValue } from '../../../../../util/constants.js';
+import {
+ ScalarValue,
+ VectorValue,
+ f16,
+ f32,
+ i32,
+ toVector,
+ u32,
+ abstractFloat,
+ abstractInt,
+} from '../../../../../util/conversion.js';
+import { FP, FPInterval } from '../../../../../util/floating_point.js';
+import {
+ cartesianProduct,
+ fullI32Range,
+ fullU32Range,
+ isFiniteF16,
+ isFiniteF32,
+ isSubnormalNumberF16,
+ isSubnormalNumberF32,
+ linearRange,
+ scalarF16Range,
+ scalarF32Range,
+} from '../../../../../util/math.js';
+import {
+ reinterpretF16AsU16,
+ reinterpretF32AsI32,
+ reinterpretF32AsU32,
+ reinterpretI32AsF32,
+ reinterpretI32AsU32,
+ reinterpretU16AsF16,
+ reinterpretU32AsF32,
+ reinterpretU32AsI32,
+} from '../../../../../util/reinterpret.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+const numNaNs = 11;
+const f32InfAndNaNInU32: number[] = [
+ // Cover NaNs evenly in integer space.
+ // The positive NaN with the lowest integer representation is the integer
+ // for infinity, plus one.
+ // The positive NaN with the highest integer representation is i32.max (!)
+ ...linearRange(kBit.f32.positive.infinity + 1, kBit.i32.positive.max, numNaNs),
+ // The negative NaN with the lowest integer representation is the integer
+ // for negative infinity, plus one.
+ // The negative NaN with the highest integer representation is u32.max (!)
+ ...linearRange(kBit.f32.negative.infinity + 1, kBit.u32.max, numNaNs),
+ kBit.f32.positive.infinity,
+ kBit.f32.negative.infinity,
+];
+const f32InfAndNaNInF32 = f32InfAndNaNInU32.map(u => reinterpretU32AsF32(u));
+const f32InfAndNaNInI32 = f32InfAndNaNInU32.map(u => reinterpretU32AsI32(u));
+
+const f32ZerosInU32 = [0, kBit.f32.negative.zero];
+const f32ZerosInF32 = f32ZerosInU32.map(u => reinterpretU32AsF32(u));
+const f32ZerosInI32 = f32ZerosInU32.map(u => reinterpretU32AsI32(u));
+const f32ZerosInterval: FPInterval = new FPInterval('f32', -0.0, 0.0);
+
+// f32FiniteRange is a list of finite f32s. fullF32Range() already
+// has +0, we only need to add -0.
+const f32FiniteRange: number[] = [...scalarF32Range(), kValue.f32.negative.zero];
+const f32RangeWithInfAndNaN: number[] = [...f32FiniteRange, ...f32InfAndNaNInF32];
+
+// Type.f16 values, finite, Inf/NaN, and zeros. Represented in float and u16.
+const f16FiniteInF16: number[] = [...scalarF16Range(), kValue.f16.negative.zero];
+const f16FiniteInU16: number[] = f16FiniteInF16.map(u => reinterpretF16AsU16(u));
+
+const f16InfAndNaNInU16: number[] = [
+ // Cover NaNs evenly in integer space.
+ // The positive NaN with the lowest integer representation is the integer
+ // for infinity, plus one.
+ // The positive NaN with the highest integer representation is u16 0x7fff i.e. 32767.
+ ...linearRange(kBit.f16.positive.infinity + 1, 32767, numNaNs).map(v => Math.ceil(v)),
+ // The negative NaN with the lowest integer representation is the integer
+ // for negative infinity, plus one.
+ // The negative NaN with the highest integer representation is u16 0xffff i.e. 65535
+ ...linearRange(kBit.f16.negative.infinity + 1, 65535, numNaNs).map(v => Math.floor(v)),
+ kBit.f16.positive.infinity,
+ kBit.f16.negative.infinity,
+];
+const f16InfAndNaNInF16 = f16InfAndNaNInU16.map(u => reinterpretU16AsF16(u));
+
+const f16ZerosInU16 = [kBit.f16.negative.zero, 0];
+
+// f16 interval that match +/-0.0.
+const f16ZerosInterval: FPInterval = new FPInterval('f16', -0.0, 0.0);
+
+/**
+ * @returns an u32 whose lower and higher 16bits are the two elements of the
+ * given array of two u16 respectively, in little-endian.
+ */
+function u16x2ToU32(u16x2: readonly number[]): number {
+ assert(u16x2.length === 2);
+ // Create a DataView with 4 bytes buffer.
+ const buffer = new ArrayBuffer(4);
+ const view = new DataView(buffer);
+ // Enforce little-endian.
+ view.setUint16(0, u16x2[0], true);
+ view.setUint16(2, u16x2[1], true);
+ return view.getUint32(0, true);
+}
+
+/**
+ * @returns an array of two u16, respectively the lower and higher 16bits of
+ * given u32 in little-endian.
+ */
+function u32ToU16x2(u32: number): number[] {
+ // Create a DataView with 4 bytes buffer.
+ const buffer = new ArrayBuffer(4);
+ const view = new DataView(buffer);
+ // Enforce little-endian.
+ view.setUint32(0, u32, true);
+ return [view.getUint16(0, true), view.getUint16(2, true)];
+}
+
+/**
+ * @returns a vec2<f16> from an array of two u16, each reinterpreted as f16.
+ */
+function u16x2ToVec2F16(u16x2: number[]): VectorValue {
+ assert(u16x2.length === 2);
+ return toVector(u16x2.map(reinterpretU16AsF16), f16);
+}
+
+/**
+ * @returns a vec4<f16> from an array of four u16, each reinterpreted as f16.
+ */
+function u16x4ToVec4F16(u16x4: number[]): VectorValue {
+ assert(u16x4.length === 4);
+ return toVector(u16x4.map(reinterpretU16AsF16), f16);
+}
+
+/**
+ * @returns true if and only if a given u32 can bitcast to a vec2<f16> with all elements
+ * being finite f16 values.
+ */
+function canU32BitcastToFiniteVec2F16(u32: number): boolean {
+ return u32ToU16x2(u32)
+ .map(u16 => isFiniteF16(reinterpretU16AsF16(u16)))
+ .reduce((a, b) => a && b, true);
+}
+
+/**
+ * @returns an array of N elements with the i-th element being an array of len elements
+ * [a_i, a_((i+1)%N), ..., a_((i+len-1)%N)], for the input array of N element [a_1, ... a_N]
+ * and the given len. For example, slidingSlice([1, 2, 3], 2) result in
+ * [[1, 2], [2, 3], [3, 1]].
+ * This helper function is used for generating vector cases from scalar values array.
+ */
+function slidingSlice(input: number[], len: number) {
+ const result: number[][] = [];
+ for (let i = 0; i < input.length; i++) {
+ const sub: number[] = [];
+ for (let j = 0; j < len; j++) {
+ sub.push(input[(i + j) % input.length]);
+ }
+ result.push(sub);
+ }
+ return result;
+}
+
+// vec2<f16> interesting (zeros, Inf, and NaN) values for testing cases.
+// vec2<f16> values that has at least one Inf/NaN f16 element, reinterpreted as u32/i32.
+const f16Vec2InfAndNaNInU32 = [
+ ...cartesianProduct(f16InfAndNaNInU16, [...f16InfAndNaNInU16, ...f16FiniteInU16]),
+ ...cartesianProduct(f16FiniteInU16, f16InfAndNaNInU16),
+].map(u16x2ToU32);
+const f16Vec2InfAndNaNInI32 = f16Vec2InfAndNaNInU32.map(u => reinterpretU32AsI32(u));
+// vec2<f16> values with two f16 0.0 element, reinterpreted as u32/i32.
+const f16Vec2ZerosInU32 = cartesianProduct(f16ZerosInU16, f16ZerosInU16).map(u16x2ToU32);
+const f16Vec2ZerosInI32 = f16Vec2ZerosInU32.map(u => reinterpretU32AsI32(u));
+
+// i32/u32/f32 range for bitcasting to vec2<f16>
+// u32 values for bitcasting to vec2<f16> finite, Inf, and NaN.
+const u32RangeForF16Vec2FiniteInfNaN: number[] = [
+ ...fullU32Range(),
+ ...f16Vec2ZerosInU32,
+ ...f16Vec2InfAndNaNInU32,
+];
+// u32 values for bitcasting to finite only vec2<f16>, used for constant evaluation.
+const u32RangeForF16Vec2Finite: number[] = u32RangeForF16Vec2FiniteInfNaN.filter(
+ canU32BitcastToFiniteVec2F16
+);
+// i32 values for bitcasting to vec2<f16> finite, zeros, Inf, and NaN.
+const i32RangeForF16Vec2FiniteInfNaN: number[] = [
+ ...fullI32Range(),
+ ...f16Vec2ZerosInI32,
+ ...f16Vec2InfAndNaNInI32,
+];
+// i32 values for bitcasting to finite only vec2<f16>, used for constant evaluation.
+const i32RangeForF16Vec2Finite: number[] = i32RangeForF16Vec2FiniteInfNaN.filter(u =>
+ canU32BitcastToFiniteVec2F16(reinterpretI32AsU32(u))
+);
+// f32 values with finite/Inf/NaN f32, for bitcasting to vec2<f16> finite, zeros, Inf, and NaN.
+const f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN: number[] = [
+ ...f32RangeWithInfAndNaN,
+ ...u32RangeForF16Vec2FiniteInfNaN.map(reinterpretU32AsF32),
+];
+// Finite f32 values for bitcasting to finite only vec2<f16>, used for constant evaluation.
+const f32FiniteRangeForF16Vec2Finite: number[] = f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN
+ .filter(isFiniteF32)
+ .filter(u => canU32BitcastToFiniteVec2F16(reinterpretF32AsU32(u)));
+
+// vec2<f16> cases for bitcasting to i32/u32/f32, by combining f16 values into pairs
+const f16Vec2FiniteInU16x2 = slidingSlice(f16FiniteInU16, 2);
+const f16Vec2FiniteInfNanInU16x2 = slidingSlice([...f16FiniteInU16, ...f16InfAndNaNInU16], 2);
+// vec4<f16> cases for bitcasting to vec2<i32/u32/f32>, by combining f16 values 4-by-4
+const f16Vec2FiniteInU16x4 = slidingSlice(f16FiniteInU16, 4);
+const f16Vec2FiniteInfNanInU16x4 = slidingSlice([...f16FiniteInU16, ...f16InfAndNaNInU16], 4);
+
+// alwaysPass comparator for i32/u32/f32 cases. For f32/f16 we also use unbound interval, which
+// allow per-element unbounded expectation for vector.
+const anyF32 = alwaysPass('any f32');
+const anyI32 = alwaysPass('any i32');
+const anyU32 = alwaysPass('any u32');
+
+// Unbounded FPInterval
+const f32UnboundedInterval = FP.f32.constants().unboundedInterval;
+const f16UnboundedInterval = FP.f16.constants().unboundedInterval;
+
+// i32 and u32 cases for bitcasting to f32.
+// i32 cases for bitcasting to f32 finite, zeros, Inf, and NaN.
+const i32RangeForF32FiniteInfNaN: number[] = [
+ ...fullI32Range(),
+ ...f32ZerosInI32,
+ ...f32InfAndNaNInI32,
+];
+// i32 cases for bitcasting to f32 finite only.
+const i32RangeForF32Finite: number[] = i32RangeForF32FiniteInfNaN.filter(i =>
+ isFiniteF32(reinterpretI32AsF32(i))
+);
+// u32 cases for bitcasting to f32 finite, zeros, Inf, and NaN.
+const u32RangeForF32FiniteInfNaN: number[] = [
+ ...fullU32Range(),
+ ...f32ZerosInU32,
+ ...f32InfAndNaNInU32,
+];
+// u32 cases for bitcasting to f32 finite only.
+const u32RangeForF32Finite: number[] = u32RangeForF32FiniteInfNaN.filter(u =>
+ isFiniteF32(reinterpretU32AsF32(u))
+);
+
+/**
+ * @returns a Comparator for checking if a f32 value is a valid
+ * bitcast conversion from f32.
+ */
+function bitcastF32ToF32Comparator(f: number): Comparator {
+ if (!isFiniteF32(f)) return anyF32;
+ const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])];
+ return anyOf(...acceptable.map(f32));
+}
+
+/**
+ * @returns a Comparator for checking if a u32 value is a valid
+ * bitcast conversion from f32.
+ */
+function bitcastF32ToU32Comparator(f: number): Comparator {
+ if (!isFiniteF32(f)) return anyU32;
+ const acceptable: number[] = [
+ reinterpretF32AsU32(f),
+ ...(isSubnormalNumberF32(f) ? f32ZerosInU32 : []),
+ ];
+ return anyOf(...acceptable.map(u32));
+}
+
+/**
+ * @returns a Comparator for checking if a i32 value is a valid
+ * bitcast conversion from f32.
+ */
+function bitcastF32ToI32Comparator(f: number): Comparator {
+ if (!isFiniteF32(f)) return anyI32;
+ const acceptable: number[] = [
+ reinterpretF32AsI32(f),
+ ...(isSubnormalNumberF32(f) ? f32ZerosInI32 : []),
+ ];
+ return anyOf(...acceptable.map(i32));
+}
+
+/**
+ * @returns a Comparator for checking if a f32 value is a valid
+ * bitcast conversion from i32.
+ */
+function bitcastI32ToF32Comparator(i: number): Comparator {
+ const f: number = reinterpretI32AsF32(i);
+ if (!isFiniteF32(f)) return anyI32;
+ // Positive or negative zero bit pattern map to any zero.
+ if (f32ZerosInI32.includes(i)) return anyOf(...f32ZerosInF32.map(f32));
+ const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])];
+ return anyOf(...acceptable.map(f32));
+}
+
+/**
+ * @returns a Comparator for checking if a f32 value is a valid
+ * bitcast conversion from u32.
+ */
+function bitcastU32ToF32Comparator(u: number): Comparator {
+ const f: number = reinterpretU32AsF32(u);
+ if (!isFiniteF32(f)) return anyU32;
+ // Positive or negative zero bit pattern map to any zero.
+ if (f32ZerosInU32.includes(u)) return anyOf(...f32ZerosInF32.map(f32));
+ const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])];
+ return anyOf(...acceptable.map(f32));
+}
+
+/**
+ * @returns an array of expected f16 FPInterval for the given bitcasted f16 value, which may be
+ * subnormal, Inf, or NaN. Test cases that bitcasted to vector of f16 use this function to get
+ * per-element expectation and build vector expectation using cartesianProduct.
+ */
+function generateF16ExpectationIntervals(bitcastedF16Value: number): FPInterval[] {
+ // If the bitcasted f16 value is inf or nan, the result is unbounded
+ if (!isFiniteF16(bitcastedF16Value)) {
+ return [f16UnboundedInterval];
+ }
+ // If the casted f16 value is +/-0.0, the result can be one of both. Note that in JS -0.0 === 0.0.
+ if (bitcastedF16Value === 0.0) {
+ return [f16ZerosInterval];
+ }
+ const exactInterval = FP.f16.toInterval(bitcastedF16Value);
+ // If the casted f16 value is subnormal, it also may be flushed to +/-0.0.
+ return [exactInterval, ...(isSubnormalNumberF16(bitcastedF16Value) ? [f16ZerosInterval] : [])];
+}
+
+/**
+ * @returns a Comparator for checking if a f16 value is a valid
+ * bitcast conversion from f16.
+ */
+function bitcastF16ToF16Comparator(f: number): Comparator {
+ if (!isFiniteF16(f)) return anyOf(f16UnboundedInterval);
+ return anyOf(...generateF16ExpectationIntervals(f));
+}
+
+/**
+ * @returns a Comparator for checking if a vec2<f16> is a valid bitcast
+ * conversion from u32.
+ */
+function bitcastU32ToVec2F16Comparator(u: number): Comparator {
+ const bitcastedVec2F16InU16x2 = u32ToU16x2(u).map(reinterpretU16AsF16);
+ // Generate expection for vec2 f16 result, by generating expected intervals for each elements and
+ // then do cartesian product.
+ const expectedIntervalsCombination = cartesianProduct(
+ ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals)
+ );
+ return anyOf(...expectedIntervalsCombination);
+}
+
+/**
+ * @returns a Comparator for checking if a vec2<f16> value is a valid
+ * bitcast conversion from i32.
+ */
+function bitcastI32ToVec2F16Comparator(i: number): Comparator {
+ const bitcastedVec2F16InU16x2 = u32ToU16x2(reinterpretI32AsU32(i)).map(reinterpretU16AsF16);
+ // Generate expection for vec2 f16 result, by generating expected intervals for each elements and
+ // then do cartesian product.
+ const expectedIntervalsCombination = cartesianProduct(
+ ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals)
+ );
+ return anyOf(...expectedIntervalsCombination);
+}
+
+/**
+ * @returns a Comparator for checking if a vec2<f16> value is a valid
+ * bitcast conversion from f32.
+ */
+function bitcastF32ToVec2F16Comparator(f: number): Comparator {
+ // If input f32 is not finite, it can be evaluated to any value and thus any result f16 vec2 is
+ // possible.
+ if (!isFiniteF32(f)) {
+ return anyOf([f16UnboundedInterval, f16UnboundedInterval]);
+ }
+ const bitcastedVec2F16InU16x2 = u32ToU16x2(reinterpretF32AsU32(f)).map(reinterpretU16AsF16);
+ // Generate expection for vec2 f16 result, by generating expected intervals for each elements and
+ // then do cartesian product.
+ const expectedIntervalsCombination = cartesianProduct(
+ ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals)
+ );
+ return anyOf(...expectedIntervalsCombination);
+}
+
+/**
+ * @returns a Comparator for checking if a vec4<f16> is a valid
+ * bitcast conversion from vec2<u32>.
+ */
+function bitcastVec2U32ToVec4F16Comparator(u32x2: number[]): Comparator {
+ assert(u32x2.length === 2);
+ const bitcastedVec4F16InU16x4 = u32x2.flatMap(u32ToU16x2).map(reinterpretU16AsF16);
+ // Generate expection for vec4 f16 result, by generating expected intervals for each elements and
+ // then do cartesian product.
+ const expectedIntervalsCombination = cartesianProduct(
+ ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals)
+ );
+ return anyOf(...expectedIntervalsCombination);
+}
+
+/**
+ * @returns a Comparator for checking if a vec4<f16> is a valid
+ * bitcast conversion from vec2<i32>.
+ */
+function bitcastVec2I32ToVec4F16Comparator(i32x2: number[]): Comparator {
+ assert(i32x2.length === 2);
+ const bitcastedVec4F16InU16x4 = i32x2
+ .map(reinterpretI32AsU32)
+ .flatMap(u32ToU16x2)
+ .map(reinterpretU16AsF16);
+ // Generate expection for vec4 f16 result, by generating expected intervals for each elements and
+ // then do cartesian product.
+ const expectedIntervalsCombination = cartesianProduct(
+ ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals)
+ );
+ return anyOf(...expectedIntervalsCombination);
+}
+
+/**
+ * @returns a Comparator for checking if a vec4<f16> is a valid
+ * bitcast conversion from vec2<f32>.
+ */
+function bitcastVec2F32ToVec4F16Comparator(f32x2: number[]): Comparator {
+ assert(f32x2.length === 2);
+ const bitcastedVec4F16InU16x4 = f32x2
+ .map(reinterpretF32AsU32)
+ .flatMap(u32ToU16x2)
+ .map(reinterpretU16AsF16);
+ // Generate expection for vec4 f16 result, by generating expected intervals for each elements and
+ // then do cartesian product.
+ const expectedIntervalsCombination = cartesianProduct(
+ ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals)
+ );
+ return anyOf(...expectedIntervalsCombination);
+}
+
+// Structure that store the expectations of a single 32bit scalar/element bitcasted from two f16.
+interface ExpectionFor32BitsScalarFromF16x2 {
+ // possibleExpectations is Scalar array if the expectation is for i32/u32 and FPInterval array for
+ // f32. Note that if the expectation for i32/u32 is unbound, possibleExpectations is meaningless.
+ possibleExpectations: (ScalarValue | FPInterval)[];
+ isUnbounded: boolean;
+}
+
+/**
+ * @returns the array of possible 16bits, represented in u16, that bitcasted
+ * from a given finite f16 represented in u16, handling the possible subnormal
+ * flushing. Used to build up 32bits or larger results.
+ */
+function possibleBitsInU16FromFiniteF16InU16(f16InU16: number): number[] {
+ const h = reinterpretU16AsF16(f16InU16);
+ assert(isFiniteF16(h));
+ return [f16InU16, ...(isSubnormalNumberF16(h) ? f16ZerosInU16 : [])];
+}
+
+/**
+ * @returns the expectation for a single 32bit scalar bitcasted from given pair of
+ * f16, result in ExpectionFor32BitsScalarFromF16x2.
+ */
+function possible32BitScalarIntervalsFromF16x2(
+ f16x2InU16x2: number[],
+ type: 'i32' | 'u32' | 'f32'
+): ExpectionFor32BitsScalarFromF16x2 {
+ assert(f16x2InU16x2.length === 2);
+ let reinterpretFromU32: (x: number) => number;
+ let expectationsForValue: (x: number) => ScalarValue[] | FPInterval[];
+ let unboundedExpectations: FPInterval[] | ScalarValue[];
+ if (type === 'u32') {
+ reinterpretFromU32 = (x: number) => x;
+ expectationsForValue = x => [u32(x)];
+ // Scalar expectation can not express "unbounded" for i32 and u32, so use 0 here as a
+ // placeholder, and the possibleExpectations should be ignored if the result is unbounded.
+ unboundedExpectations = [u32(0)];
+ } else if (type === 'i32') {
+ reinterpretFromU32 = (x: number) => reinterpretU32AsI32(x);
+ expectationsForValue = x => [i32(x)];
+ // Scalar expectation can not express "unbounded" for i32 and u32, so use 0 here as a
+ // placeholder, and the possibleExpectations should be ignored if the result is unbounded.
+ unboundedExpectations = [i32(0)];
+ } else {
+ assert(type === 'f32');
+ reinterpretFromU32 = (x: number) => reinterpretU32AsF32(x);
+ expectationsForValue = x => {
+ // Handle the possible Inf/NaN/zeros and subnormal cases for f32 result.
+ if (!isFiniteF32(x)) {
+ return [f32UnboundedInterval];
+ }
+ // If the casted f16 value is +/-0.0, the result can be one of both. Note that in JS -0.0 === 0.0.
+ if (x === 0.0) {
+ return [f32ZerosInterval];
+ }
+ const exactInterval = FP.f32.toInterval(x);
+ // If the casted f16 value is subnormal, it also may be flushed to +/-0.0.
+ return [exactInterval, ...(isSubnormalNumberF32(x) ? [f32ZerosInterval] : [])];
+ };
+ unboundedExpectations = [f32UnboundedInterval];
+ }
+ // Return unbounded expection if f16 Inf/NaN occurs
+ if (
+ !isFiniteF16(reinterpretU16AsF16(f16x2InU16x2[0])) ||
+ !isFiniteF16(reinterpretU16AsF16(f16x2InU16x2[1]))
+ ) {
+ return { possibleExpectations: unboundedExpectations, isUnbounded: true };
+ }
+ const possibleU16Bits = f16x2InU16x2.map(possibleBitsInU16FromFiniteF16InU16);
+ const possibleExpectations = cartesianProduct(...possibleU16Bits).flatMap<
+ ScalarValue | FPInterval
+ >((possibleBitsU16x2: readonly number[]) => {
+ assert(possibleBitsU16x2.length === 2);
+ return expectationsForValue(reinterpretFromU32(u16x2ToU32(possibleBitsU16x2)));
+ });
+ return { possibleExpectations, isUnbounded: false };
+}
+
+/**
+ * @returns a Comparator for checking if a u32 value is a valid
+ * bitcast conversion from vec2 f16.
+ */
+function bitcastVec2F16ToU32Comparator(vec2F16InU16x2: number[]): Comparator {
+ assert(vec2F16InU16x2.length === 2);
+ const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'u32');
+ // Return alwaysPass if result is expected unbounded.
+ if (expectations.isUnbounded) {
+ return anyU32;
+ }
+ return anyOf(...expectations.possibleExpectations);
+}
+
+/**
+ * @returns a Comparator for checking if a i32 value is a valid
+ * bitcast conversion from vec2 f16.
+ */
+function bitcastVec2F16ToI32Comparator(vec2F16InU16x2: number[]): Comparator {
+ assert(vec2F16InU16x2.length === 2);
+ const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'i32');
+ // Return alwaysPass if result is expected unbounded.
+ if (expectations.isUnbounded) {
+ return anyI32;
+ }
+ return anyOf(...expectations.possibleExpectations);
+}
+
+/**
+ * @returns a Comparator for checking if a i32 value is a valid
+ * bitcast conversion from vec2 f16.
+ */
+function bitcastVec2F16ToF32Comparator(vec2F16InU16x2: number[]): Comparator {
+ assert(vec2F16InU16x2.length === 2);
+ const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'f32');
+ // Return alwaysPass if result is expected unbounded.
+ if (expectations.isUnbounded) {
+ return anyF32;
+ }
+ return anyOf(...expectations.possibleExpectations);
+}
+
+/**
+ * @returns a Comparator for checking if a vec2 u32 value is a valid
+ * bitcast conversion from vec4 f16.
+ */
+function bitcastVec4F16ToVec2U32Comparator(vec4F16InU16x4: number[]): Comparator {
+ assert(vec4F16InU16x4.length === 4);
+ const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e =>
+ possible32BitScalarIntervalsFromF16x2(e, 'u32')
+ );
+ // Return alwaysPass if any element is expected unbounded. Although it may be only one unbounded
+ // element in the result vector, currently we don't have a way to build a comparator that expect
+ // only one element of i32/u32 vector unbounded.
+ if (expectationsPerElement.map(e => e.isUnbounded).reduce((a, b) => a || b, false)) {
+ return alwaysPass('any vec2<u32>');
+ }
+ return anyOf(
+ ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map(
+ e => new VectorValue(e as ScalarValue[])
+ )
+ );
+}
+
+/**
+ * @returns a Comparator for checking if a vec2 i32 value is a valid
+ * bitcast conversion from vec4 f16.
+ */
+function bitcastVec4F16ToVec2I32Comparator(vec4F16InU16x4: number[]): Comparator {
+ assert(vec4F16InU16x4.length === 4);
+ const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e =>
+ possible32BitScalarIntervalsFromF16x2(e, 'i32')
+ );
+ // Return alwaysPass if any element is expected unbounded. Although it may be only one unbounded
+ // element in the result vector, currently we don't have a way to build a comparator that expect
+ // only one element of i32/u32 vector unbounded.
+ if (expectationsPerElement.map(e => e.isUnbounded).reduce((a, b) => a || b, false)) {
+ return alwaysPass('any vec2<i32>');
+ }
+ return anyOf(
+ ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map(
+ e => new VectorValue(e as ScalarValue[])
+ )
+ );
+}
+
+/**
+ * @returns a Comparator for checking if a vec2 f32 value is a valid
+ * bitcast conversion from vec4 f16.
+ */
+function bitcastVec4F16ToVec2F32Comparator(vec4F16InU16x4: number[]): Comparator {
+ assert(vec4F16InU16x4.length === 4);
+ const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e =>
+ possible32BitScalarIntervalsFromF16x2(e, 'f32')
+ );
+ return anyOf(
+ ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map(e => [
+ e[0] as FPInterval,
+ e[1] as FPInterval,
+ ])
+ );
+}
+
+export const d = makeCaseCache('bitcast', {
+ // Identity Cases
+ i32_to_i32: () => fullI32Range().map(e => ({ input: i32(e), expected: i32(e) })),
+ u32_to_u32: () => fullU32Range().map(e => ({ input: u32(e), expected: u32(e) })),
+ f32_inf_nan_to_f32: () =>
+ f32RangeWithInfAndNaN.map(e => ({
+ input: f32(e),
+ expected: bitcastF32ToF32Comparator(e),
+ })),
+ f32_to_f32: () =>
+ f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToF32Comparator(e) })),
+ f16_inf_nan_to_f16: () =>
+ [...f16FiniteInF16, ...f16InfAndNaNInF16].map(e => ({
+ input: f16(e),
+ expected: bitcastF16ToF16Comparator(e),
+ })),
+ f16_to_f16: () =>
+ f16FiniteInF16.map(e => ({ input: f16(e), expected: bitcastF16ToF16Comparator(e) })),
+
+ // i32,u32,f32,Abstract to different i32,u32,f32
+ i32_to_u32: () => fullI32Range().map(e => ({ input: i32(e), expected: u32(e) })),
+ i32_to_f32: () =>
+ i32RangeForF32Finite.map(e => ({
+ input: i32(e),
+ expected: bitcastI32ToF32Comparator(e),
+ })),
+ ai_to_i32: () => fullI32Range().map(e => ({ input: abstractInt(BigInt(e)), expected: i32(e) })),
+ ai_to_u32: () => fullU32Range().map(e => ({ input: abstractInt(BigInt(e)), expected: u32(e) })),
+ ai_to_f32: () =>
+ // AbstractInt is converted to i32, because there is no explicit overload
+ i32RangeForF32Finite.map(e => ({
+ input: abstractInt(BigInt(e)),
+ expected: bitcastI32ToF32Comparator(e),
+ })),
+ i32_to_f32_inf_nan: () =>
+ i32RangeForF32FiniteInfNaN.map(e => ({
+ input: i32(e),
+ expected: bitcastI32ToF32Comparator(e),
+ })),
+ u32_to_i32: () => fullU32Range().map(e => ({ input: u32(e), expected: i32(e) })),
+ u32_to_f32: () =>
+ u32RangeForF32Finite.map(e => ({
+ input: u32(e),
+ expected: bitcastU32ToF32Comparator(e),
+ })),
+ u32_to_f32_inf_nan: () =>
+ u32RangeForF32FiniteInfNaN.map(e => ({
+ input: u32(e),
+ expected: bitcastU32ToF32Comparator(e),
+ })),
+ f32_inf_nan_to_i32: () =>
+ f32RangeWithInfAndNaN.map(e => ({
+ input: f32(e),
+ expected: bitcastF32ToI32Comparator(e),
+ })),
+ f32_to_i32: () =>
+ f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToI32Comparator(e) })),
+
+ f32_inf_nan_to_u32: () =>
+ f32RangeWithInfAndNaN.map(e => ({
+ input: f32(e),
+ expected: bitcastF32ToU32Comparator(e),
+ })),
+ f32_to_u32: () =>
+ f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToU32Comparator(e) })),
+
+ // i32,u32,f32,AbstractInt to vec2<f16>
+ u32_to_vec2_f16_inf_nan: () =>
+ u32RangeForF16Vec2FiniteInfNaN.map(e => ({
+ input: u32(e),
+ expected: bitcastU32ToVec2F16Comparator(e),
+ })),
+ u32_to_vec2_f16: () =>
+ u32RangeForF16Vec2Finite.map(e => ({
+ input: u32(e),
+ expected: bitcastU32ToVec2F16Comparator(e),
+ })),
+ i32_to_vec2_f16_inf_nan: () =>
+ i32RangeForF16Vec2FiniteInfNaN.map(e => ({
+ input: i32(e),
+ expected: bitcastI32ToVec2F16Comparator(e),
+ })),
+ i32_to_vec2_f16: () =>
+ i32RangeForF16Vec2Finite.map(e => ({
+ input: i32(e),
+ expected: bitcastI32ToVec2F16Comparator(e),
+ })),
+ ai_to_vec2_f16: () =>
+ // AbstractInt is converted to i32, because there is no explicit overload
+ i32RangeForF16Vec2Finite.map(e => ({
+ input: abstractInt(BigInt(e)),
+ expected: bitcastI32ToVec2F16Comparator(e),
+ })),
+ f32_inf_nan_to_vec2_f16_inf_nan: () =>
+ f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN.map(e => ({
+ input: f32(e),
+ expected: bitcastF32ToVec2F16Comparator(e),
+ })),
+ f32_to_vec2_f16: () =>
+ f32FiniteRangeForF16Vec2Finite.map(e => ({
+ input: f32(e),
+ expected: bitcastF32ToVec2F16Comparator(e),
+ })),
+ af_to_vec2_f16: () =>
+ f32FiniteRangeForF16Vec2Finite.map(e => ({
+ input: abstractFloat(e),
+ expected: bitcastF32ToVec2F16Comparator(e),
+ })),
+
+ // vec2<i32>, vec2<u32>, vec2<f32>, vec2<AbstractInt> to vec4<f16>
+ vec2_i32_to_vec4_f16_inf_nan: () =>
+ slidingSlice(i32RangeForF16Vec2FiniteInfNaN, 2).map(e => ({
+ input: toVector(e, i32),
+ expected: bitcastVec2I32ToVec4F16Comparator(e),
+ })),
+ vec2_i32_to_vec4_f16: () =>
+ slidingSlice(i32RangeForF16Vec2Finite, 2).map(e => ({
+ input: toVector(e, i32),
+ expected: bitcastVec2I32ToVec4F16Comparator(e),
+ })),
+ vec2_ai_to_vec4_f16: () =>
+ // AbstractInt is converted to i32, because there is no explicit overload
+ slidingSlice(i32RangeForF16Vec2Finite, 2).map(e => ({
+ input: toVector(e, (n: number) => abstractInt(BigInt(n))),
+ expected: bitcastVec2I32ToVec4F16Comparator(e),
+ })),
+ vec2_u32_to_vec4_f16_inf_nan: () =>
+ slidingSlice(u32RangeForF16Vec2FiniteInfNaN, 2).map(e => ({
+ input: toVector(e, u32),
+ expected: bitcastVec2U32ToVec4F16Comparator(e),
+ })),
+ vec2_u32_to_vec4_f16: () =>
+ slidingSlice(u32RangeForF16Vec2Finite, 2).map(e => ({
+ input: toVector(e, u32),
+ expected: bitcastVec2U32ToVec4F16Comparator(e),
+ })),
+ vec2_f32_inf_nan_to_vec4_f16_inf_nan: () =>
+ slidingSlice(f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN, 2).map(e => ({
+ input: toVector(e, f32),
+ expected: bitcastVec2F32ToVec4F16Comparator(e),
+ })),
+ vec2_f32_to_vec4_f16: () =>
+ slidingSlice(f32FiniteRangeForF16Vec2Finite, 2).map(e => ({
+ input: toVector(e, f32),
+ expected: bitcastVec2F32ToVec4F16Comparator(e),
+ })),
+ vec2_af_to_vec4_f16: () =>
+ slidingSlice(f32FiniteRangeForF16Vec2Finite, 2).map(e => ({
+ input: toVector(e, abstractFloat),
+ expected: bitcastVec2F32ToVec4F16Comparator(e),
+ })),
+
+ // vec2<f16> to i32, u32, f32
+ vec2_f16_to_u32: () =>
+ f16Vec2FiniteInU16x2.map(e => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToU32Comparator(e),
+ })),
+ vec2_f16_inf_nan_to_u32: () =>
+ f16Vec2FiniteInfNanInU16x2.map(e => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToU32Comparator(e),
+ })),
+ vec2_f16_to_i32: () =>
+ f16Vec2FiniteInU16x2.map(e => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToI32Comparator(e),
+ })),
+ vec2_f16_inf_nan_to_i32: () =>
+ f16Vec2FiniteInfNanInU16x2.map(e => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToI32Comparator(e),
+ })),
+ vec2_f16_to_f32_finite: () =>
+ f16Vec2FiniteInU16x2
+ .filter(u16x2 => isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x2))))
+ .map(e => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToF32Comparator(e),
+ })),
+ vec2_f16_inf_nan_to_f32: () =>
+ f16Vec2FiniteInfNanInU16x2.map(e => ({
+ input: u16x2ToVec2F16(e),
+ expected: bitcastVec2F16ToF32Comparator(e),
+ })),
+
+ // vec4<f16> to vec2 of i32, u32, f32
+ vec4_f16_to_vec2_u32: () =>
+ f16Vec2FiniteInU16x4.map(e => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2U32Comparator(e),
+ })),
+ vec4_f16_inf_nan_to_vec2_u32: () =>
+ f16Vec2FiniteInfNanInU16x4.map(e => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2U32Comparator(e),
+ })),
+ vec4_f16_to_vec2_i32: () =>
+ f16Vec2FiniteInU16x4.map(e => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2I32Comparator(e),
+ })),
+ vec4_f16_inf_nan_to_vec2_i32: () =>
+ f16Vec2FiniteInfNanInU16x4.map(e => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2I32Comparator(e),
+ })),
+ vec4_f16_to_vec2_f32_finite: () =>
+ f16Vec2FiniteInU16x4
+ .filter(
+ u16x4 =>
+ isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x4.slice(0, 2)))) &&
+ isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x4.slice(2, 4))))
+ )
+ .map(e => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2F32Comparator(e),
+ })),
+ vec4_f16_inf_nan_to_vec2_f32: () =>
+ f16Vec2FiniteInfNanInU16x4.map(e => ({
+ input: u16x4ToVec4F16(e),
+ expected: bitcastVec4F16ToVec2F32Comparator(e),
+ })),
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts
index 390129f2c7..02acf98ef4 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/bitcast.spec.ts
@@ -11,6 +11,9 @@ S is i32, u32, f32
T is i32, u32, f32, and T is not S
Reinterpretation of bits. Beware non-normal f32 values.
+@const @must_use fn bitcast<u32>(e : Type.abstractInt) -> T
+@const @must_use fn bitcast<vecN<u32>>(e : vecN<Type.abstractInt>) -> T
+
@const @must_use fn bitcast<T>(e: vec2<f16> ) -> T
@const @must_use fn bitcast<vec2<T>>(e: vec4<f16> ) -> vec2<T>
@const @must_use fn bitcast<vec2<f16>>(e: T ) -> vec2<f16>
@@ -20,823 +23,26 @@ T is i32, u32, f32
import { TestParams } from '../../../../../../common/framework/fixture.js';
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
-import { assert } from '../../../../../../common/util/util.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { Comparator, alwaysPass, anyOf } from '../../../../../util/compare.js';
-import { kBit, kValue } from '../../../../../util/constants.js';
+import { anyOf } from '../../../../../util/compare.js';
import {
f32,
- i32,
u32,
- f16,
- TypeF32,
- TypeI32,
- TypeU32,
- TypeF16,
- TypeVec,
- Vector,
- Scalar,
- toVector,
+ i32,
+ abstractFloat,
+ uint32ToFloat32,
+ u32Bits,
+ Type,
} from '../../../../../util/conversion.js';
-import { FPInterval, FP } from '../../../../../util/floating_point.js';
-import {
- fullF32Range,
- fullI32Range,
- fullU32Range,
- fullF16Range,
- linearRange,
- isSubnormalNumberF32,
- isSubnormalNumberF16,
- cartesianProduct,
- isFiniteF32,
- isFiniteF16,
-} from '../../../../../util/math.js';
-import {
- reinterpretI32AsF32,
- reinterpretI32AsU32,
- reinterpretF32AsI32,
- reinterpretF32AsU32,
- reinterpretU32AsF32,
- reinterpretU32AsI32,
- reinterpretU16AsF16,
- reinterpretF16AsU16,
-} from '../../../../../util/reinterpret.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run, ShaderBuilder } from '../../expression.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { scalarF32Range } from '../../../../../util/math.js';
+import { ShaderBuilder, allInputSources, onlyConstInputSource, run } from '../../expression.js';
+import { d } from './bitcast.cache.js';
import { builtinWithPredeclaration } from './builtin.js';
export const g = makeTestGroup(GPUTest);
-const numNaNs = 11;
-const f32InfAndNaNInU32: number[] = [
- // Cover NaNs evenly in integer space.
- // The positive NaN with the lowest integer representation is the integer
- // for infinity, plus one.
- // The positive NaN with the highest integer representation is i32.max (!)
- ...linearRange(kBit.f32.positive.infinity + 1, kBit.i32.positive.max, numNaNs),
- // The negative NaN with the lowest integer representation is the integer
- // for negative infinity, plus one.
- // The negative NaN with the highest integer representation is u32.max (!)
- ...linearRange(kBit.f32.negative.infinity + 1, kBit.u32.max, numNaNs),
- kBit.f32.positive.infinity,
- kBit.f32.negative.infinity,
-];
-const f32InfAndNaNInF32 = f32InfAndNaNInU32.map(u => reinterpretU32AsF32(u));
-const f32InfAndNaNInI32 = f32InfAndNaNInU32.map(u => reinterpretU32AsI32(u));
-
-const f32ZerosInU32 = [0, kBit.f32.negative.zero];
-const f32ZerosInF32 = f32ZerosInU32.map(u => reinterpretU32AsF32(u));
-const f32ZerosInI32 = f32ZerosInU32.map(u => reinterpretU32AsI32(u));
-const f32ZerosInterval: FPInterval = new FPInterval('f32', -0.0, 0.0);
-
-// f32FiniteRange is a list of finite f32s. fullF32Range() already
-// has +0, we only need to add -0.
-const f32FiniteRange: number[] = [...fullF32Range(), kValue.f32.negative.zero];
-const f32RangeWithInfAndNaN: number[] = [...f32FiniteRange, ...f32InfAndNaNInF32];
-
-// F16 values, finite, Inf/NaN, and zeros. Represented in float and u16.
-const f16FiniteInF16: number[] = [...fullF16Range(), kValue.f16.negative.zero];
-const f16FiniteInU16: number[] = f16FiniteInF16.map(u => reinterpretF16AsU16(u));
-
-const f16InfAndNaNInU16: number[] = [
- // Cover NaNs evenly in integer space.
- // The positive NaN with the lowest integer representation is the integer
- // for infinity, plus one.
- // The positive NaN with the highest integer representation is u16 0x7fff i.e. 32767.
- ...linearRange(kBit.f16.positive.infinity + 1, 32767, numNaNs).map(v => Math.ceil(v)),
- // The negative NaN with the lowest integer representation is the integer
- // for negative infinity, plus one.
- // The negative NaN with the highest integer representation is u16 0xffff i.e. 65535
- ...linearRange(kBit.f16.negative.infinity + 1, 65535, numNaNs).map(v => Math.floor(v)),
- kBit.f16.positive.infinity,
- kBit.f16.negative.infinity,
-];
-const f16InfAndNaNInF16 = f16InfAndNaNInU16.map(u => reinterpretU16AsF16(u));
-
-const f16ZerosInU16 = [kBit.f16.negative.zero, 0];
-
-// f16 interval that match +/-0.0.
-const f16ZerosInterval: FPInterval = new FPInterval('f16', -0.0, 0.0);
-
-/**
- * @returns an u32 whose lower and higher 16bits are the two elements of the
- * given array of two u16 respectively, in little-endian.
- */
-function u16x2ToU32(u16x2: readonly number[]): number {
- assert(u16x2.length === 2);
- // Create a DataView with 4 bytes buffer.
- const buffer = new ArrayBuffer(4);
- const view = new DataView(buffer);
- // Enforce little-endian.
- view.setUint16(0, u16x2[0], true);
- view.setUint16(2, u16x2[1], true);
- return view.getUint32(0, true);
-}
-
-/**
- * @returns an array of two u16, respectively the lower and higher 16bits of
- * given u32 in little-endian.
- */
-function u32ToU16x2(u32: number): number[] {
- // Create a DataView with 4 bytes buffer.
- const buffer = new ArrayBuffer(4);
- const view = new DataView(buffer);
- // Enforce little-endian.
- view.setUint32(0, u32, true);
- return [view.getUint16(0, true), view.getUint16(2, true)];
-}
-
-/**
- * @returns a vec2<f16> from an array of two u16, each reinterpreted as f16.
- */
-function u16x2ToVec2F16(u16x2: number[]): Vector {
- assert(u16x2.length === 2);
- return toVector(u16x2.map(reinterpretU16AsF16), f16);
-}
-
-/**
- * @returns a vec4<f16> from an array of four u16, each reinterpreted as f16.
- */
-function u16x4ToVec4F16(u16x4: number[]): Vector {
- assert(u16x4.length === 4);
- return toVector(u16x4.map(reinterpretU16AsF16), f16);
-}
-
-/**
- * @returns true if and only if a given u32 can bitcast to a vec2<f16> with all elements
- * being finite f16 values.
- */
-function canU32BitcastToFiniteVec2F16(u32: number): boolean {
- return u32ToU16x2(u32)
- .map(u16 => isFiniteF16(reinterpretU16AsF16(u16)))
- .reduce((a, b) => a && b, true);
-}
-
-/**
- * @returns an array of N elements with the i-th element being an array of len elements
- * [a_i, a_((i+1)%N), ..., a_((i+len-1)%N)], for the input array of N element [a_1, ... a_N]
- * and the given len. For example, slidingSlice([1, 2, 3], 2) result in
- * [[1, 2], [2, 3], [3, 1]].
- * This helper function is used for generating vector cases from scalar values array.
- */
-function slidingSlice(input: number[], len: number) {
- const result: number[][] = [];
- for (let i = 0; i < input.length; i++) {
- const sub: number[] = [];
- for (let j = 0; j < len; j++) {
- sub.push(input[(i + j) % input.length]);
- }
- result.push(sub);
- }
- return result;
-}
-
-// vec2<f16> interesting (zeros, Inf, and NaN) values for testing cases.
-// vec2<f16> values that has at least one Inf/NaN f16 element, reinterpreted as u32/i32.
-const f16Vec2InfAndNaNInU32 = [
- ...cartesianProduct(f16InfAndNaNInU16, [...f16InfAndNaNInU16, ...f16FiniteInU16]),
- ...cartesianProduct(f16FiniteInU16, f16InfAndNaNInU16),
-].map(u16x2ToU32);
-const f16Vec2InfAndNaNInI32 = f16Vec2InfAndNaNInU32.map(u => reinterpretU32AsI32(u));
-// vec2<f16> values with two f16 0.0 element, reinterpreted as u32/i32.
-const f16Vec2ZerosInU32 = cartesianProduct(f16ZerosInU16, f16ZerosInU16).map(u16x2ToU32);
-const f16Vec2ZerosInI32 = f16Vec2ZerosInU32.map(u => reinterpretU32AsI32(u));
-
-// i32/u32/f32 range for bitcasting to vec2<f16>
-// u32 values for bitcasting to vec2<f16> finite, Inf, and NaN.
-const u32RangeForF16Vec2FiniteInfNaN: number[] = [
- ...fullU32Range(),
- ...f16Vec2ZerosInU32,
- ...f16Vec2InfAndNaNInU32,
-];
-// u32 values for bitcasting to finite only vec2<f16>, used for constant evaluation.
-const u32RangeForF16Vec2Finite: number[] = u32RangeForF16Vec2FiniteInfNaN.filter(
- canU32BitcastToFiniteVec2F16
-);
-// i32 values for bitcasting to vec2<f16> finite, zeros, Inf, and NaN.
-const i32RangeForF16Vec2FiniteInfNaN: number[] = [
- ...fullI32Range(),
- ...f16Vec2ZerosInI32,
- ...f16Vec2InfAndNaNInI32,
-];
-// i32 values for bitcasting to finite only vec2<f16>, used for constant evaluation.
-const i32RangeForF16Vec2Finite: number[] = i32RangeForF16Vec2FiniteInfNaN.filter(u =>
- canU32BitcastToFiniteVec2F16(reinterpretI32AsU32(u))
-);
-// f32 values with finite/Inf/NaN f32, for bitcasting to vec2<f16> finite, zeros, Inf, and NaN.
-const f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN: number[] = [
- ...f32RangeWithInfAndNaN,
- ...u32RangeForF16Vec2FiniteInfNaN.map(reinterpretU32AsF32),
-];
-// Finite f32 values for bitcasting to finite only vec2<f16>, used for constant evaluation.
-const f32FiniteRangeForF16Vec2Finite: number[] = f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN
- .filter(isFiniteF32)
- .filter(u => canU32BitcastToFiniteVec2F16(reinterpretF32AsU32(u)));
-
-// vec2<f16> cases for bitcasting to i32/u32/f32, by combining f16 values into pairs
-const f16Vec2FiniteInU16x2 = slidingSlice(f16FiniteInU16, 2);
-const f16Vec2FiniteInfNanInU16x2 = slidingSlice([...f16FiniteInU16, ...f16InfAndNaNInU16], 2);
-// vec4<f16> cases for bitcasting to vec2<i32/u32/f32>, by combining f16 values 4-by-4
-const f16Vec2FiniteInU16x4 = slidingSlice(f16FiniteInU16, 4);
-const f16Vec2FiniteInfNanInU16x4 = slidingSlice([...f16FiniteInU16, ...f16InfAndNaNInU16], 4);
-
-// alwaysPass comparator for i32/u32/f32 cases. For f32/f16 we also use unbound interval, which
-// allow per-element unbounded expectation for vector.
-const anyF32 = alwaysPass('any f32');
-const anyI32 = alwaysPass('any i32');
-const anyU32 = alwaysPass('any u32');
-
-// Unbounded FPInterval
-const f32UnboundedInterval = FP.f32.constants().unboundedInterval;
-const f16UnboundedInterval = FP.f16.constants().unboundedInterval;
-
-// i32 and u32 cases for bitcasting to f32.
-// i32 cases for bitcasting to f32 finite, zeros, Inf, and NaN.
-const i32RangeForF32FiniteInfNaN: number[] = [
- ...fullI32Range(),
- ...f32ZerosInI32,
- ...f32InfAndNaNInI32,
-];
-// i32 cases for bitcasting to f32 finite only.
-const i32RangeForF32Finite: number[] = i32RangeForF32FiniteInfNaN.filter(i =>
- isFiniteF32(reinterpretI32AsF32(i))
-);
-// u32 cases for bitcasting to f32 finite, zeros, Inf, and NaN.
-const u32RangeForF32FiniteInfNaN: number[] = [
- ...fullU32Range(),
- ...f32ZerosInU32,
- ...f32InfAndNaNInU32,
-];
-// u32 cases for bitcasting to f32 finite only.
-const u32RangeForF32Finite: number[] = u32RangeForF32FiniteInfNaN.filter(u =>
- isFiniteF32(reinterpretU32AsF32(u))
-);
-
-/**
- * @returns a Comparator for checking if a f32 value is a valid
- * bitcast conversion from f32.
- */
-function bitcastF32ToF32Comparator(f: number): Comparator {
- if (!isFiniteF32(f)) return anyF32;
- const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])];
- return anyOf(...acceptable.map(f32));
-}
-
-/**
- * @returns a Comparator for checking if a u32 value is a valid
- * bitcast conversion from f32.
- */
-function bitcastF32ToU32Comparator(f: number): Comparator {
- if (!isFiniteF32(f)) return anyU32;
- const acceptable: number[] = [
- reinterpretF32AsU32(f),
- ...(isSubnormalNumberF32(f) ? f32ZerosInU32 : []),
- ];
- return anyOf(...acceptable.map(u32));
-}
-
-/**
- * @returns a Comparator for checking if a i32 value is a valid
- * bitcast conversion from f32.
- */
-function bitcastF32ToI32Comparator(f: number): Comparator {
- if (!isFiniteF32(f)) return anyI32;
- const acceptable: number[] = [
- reinterpretF32AsI32(f),
- ...(isSubnormalNumberF32(f) ? f32ZerosInI32 : []),
- ];
- return anyOf(...acceptable.map(i32));
-}
-
-/**
- * @returns a Comparator for checking if a f32 value is a valid
- * bitcast conversion from i32.
- */
-function bitcastI32ToF32Comparator(i: number): Comparator {
- const f: number = reinterpretI32AsF32(i);
- if (!isFiniteF32(f)) return anyI32;
- // Positive or negative zero bit pattern map to any zero.
- if (f32ZerosInI32.includes(i)) return anyOf(...f32ZerosInF32.map(f32));
- const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])];
- return anyOf(...acceptable.map(f32));
-}
-
-/**
- * @returns a Comparator for checking if a f32 value is a valid
- * bitcast conversion from u32.
- */
-function bitcastU32ToF32Comparator(u: number): Comparator {
- const f: number = reinterpretU32AsF32(u);
- if (!isFiniteF32(f)) return anyU32;
- // Positive or negative zero bit pattern map to any zero.
- if (f32ZerosInU32.includes(u)) return anyOf(...f32ZerosInF32.map(f32));
- const acceptable: number[] = [f, ...(isSubnormalNumberF32(f) ? f32ZerosInF32 : [])];
- return anyOf(...acceptable.map(f32));
-}
-
-/**
- * @returns an array of expected f16 FPInterval for the given bitcasted f16 value, which may be
- * subnormal, Inf, or NaN. Test cases that bitcasted to vector of f16 use this function to get
- * per-element expectation and build vector expectation using cartesianProduct.
- */
-function generateF16ExpectationIntervals(bitcastedF16Value: number): FPInterval[] {
- // If the bitcasted f16 value is inf or nan, the result is unbounded
- if (!isFiniteF16(bitcastedF16Value)) {
- return [f16UnboundedInterval];
- }
- // If the casted f16 value is +/-0.0, the result can be one of both. Note that in JS -0.0 === 0.0.
- if (bitcastedF16Value === 0.0) {
- return [f16ZerosInterval];
- }
- const exactInterval = FP.f16.toInterval(bitcastedF16Value);
- // If the casted f16 value is subnormal, it also may be flushed to +/-0.0.
- return [exactInterval, ...(isSubnormalNumberF16(bitcastedF16Value) ? [f16ZerosInterval] : [])];
-}
-
-/**
- * @returns a Comparator for checking if a f16 value is a valid
- * bitcast conversion from f16.
- */
-function bitcastF16ToF16Comparator(f: number): Comparator {
- if (!isFiniteF16(f)) return anyOf(f16UnboundedInterval);
- return anyOf(...generateF16ExpectationIntervals(f));
-}
-
-/**
- * @returns a Comparator for checking if a vec2<f16> is a valid bitcast
- * conversion from u32.
- */
-function bitcastU32ToVec2F16Comparator(u: number): Comparator {
- const bitcastedVec2F16InU16x2 = u32ToU16x2(u).map(reinterpretU16AsF16);
- // Generate expection for vec2 f16 result, by generating expected intervals for each elements and
- // then do cartesian product.
- const expectedIntervalsCombination = cartesianProduct(
- ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals)
- );
- return anyOf(...expectedIntervalsCombination);
-}
-
-/**
- * @returns a Comparator for checking if a vec2<f16> value is a valid
- * bitcast conversion from i32.
- */
-function bitcastI32ToVec2F16Comparator(i: number): Comparator {
- const bitcastedVec2F16InU16x2 = u32ToU16x2(reinterpretI32AsU32(i)).map(reinterpretU16AsF16);
- // Generate expection for vec2 f16 result, by generating expected intervals for each elements and
- // then do cartesian product.
- const expectedIntervalsCombination = cartesianProduct(
- ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals)
- );
- return anyOf(...expectedIntervalsCombination);
-}
-
-/**
- * @returns a Comparator for checking if a vec2<f16> value is a valid
- * bitcast conversion from f32.
- */
-function bitcastF32ToVec2F16Comparator(f: number): Comparator {
- // If input f32 is not finite, it can be evaluated to any value and thus any result f16 vec2 is
- // possible.
- if (!isFiniteF32(f)) {
- return anyOf([f16UnboundedInterval, f16UnboundedInterval]);
- }
- const bitcastedVec2F16InU16x2 = u32ToU16x2(reinterpretF32AsU32(f)).map(reinterpretU16AsF16);
- // Generate expection for vec2 f16 result, by generating expected intervals for each elements and
- // then do cartesian product.
- const expectedIntervalsCombination = cartesianProduct(
- ...bitcastedVec2F16InU16x2.map(generateF16ExpectationIntervals)
- );
- return anyOf(...expectedIntervalsCombination);
-}
-
-/**
- * @returns a Comparator for checking if a vec4<f16> is a valid
- * bitcast conversion from vec2<u32>.
- */
-function bitcastVec2U32ToVec4F16Comparator(u32x2: number[]): Comparator {
- assert(u32x2.length === 2);
- const bitcastedVec4F16InU16x4 = u32x2.flatMap(u32ToU16x2).map(reinterpretU16AsF16);
- // Generate expection for vec4 f16 result, by generating expected intervals for each elements and
- // then do cartesian product.
- const expectedIntervalsCombination = cartesianProduct(
- ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals)
- );
- return anyOf(...expectedIntervalsCombination);
-}
-
-/**
- * @returns a Comparator for checking if a vec4<f16> is a valid
- * bitcast conversion from vec2<i32>.
- */
-function bitcastVec2I32ToVec4F16Comparator(i32x2: number[]): Comparator {
- assert(i32x2.length === 2);
- const bitcastedVec4F16InU16x4 = i32x2
- .map(reinterpretI32AsU32)
- .flatMap(u32ToU16x2)
- .map(reinterpretU16AsF16);
- // Generate expection for vec4 f16 result, by generating expected intervals for each elements and
- // then do cartesian product.
- const expectedIntervalsCombination = cartesianProduct(
- ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals)
- );
- return anyOf(...expectedIntervalsCombination);
-}
-
-/**
- * @returns a Comparator for checking if a vec4<f16> is a valid
- * bitcast conversion from vec2<f32>.
- */
-function bitcastVec2F32ToVec4F16Comparator(f32x2: number[]): Comparator {
- assert(f32x2.length === 2);
- const bitcastedVec4F16InU16x4 = f32x2
- .map(reinterpretF32AsU32)
- .flatMap(u32ToU16x2)
- .map(reinterpretU16AsF16);
- // Generate expection for vec4 f16 result, by generating expected intervals for each elements and
- // then do cartesian product.
- const expectedIntervalsCombination = cartesianProduct(
- ...bitcastedVec4F16InU16x4.map(generateF16ExpectationIntervals)
- );
- return anyOf(...expectedIntervalsCombination);
-}
-
-// Structure that store the expectations of a single 32bit scalar/element bitcasted from two f16.
-interface ExpectionFor32BitsScalarFromF16x2 {
- // possibleExpectations is Scalar array if the expectation is for i32/u32 and FPInterval array for
- // f32. Note that if the expectation for i32/u32 is unbound, possibleExpectations is meaningless.
- possibleExpectations: (Scalar | FPInterval)[];
- isUnbounded: boolean;
-}
-
-/**
- * @returns the array of possible 16bits, represented in u16, that bitcasted
- * from a given finite f16 represented in u16, handling the possible subnormal
- * flushing. Used to build up 32bits or larger results.
- */
-function possibleBitsInU16FromFiniteF16InU16(f16InU16: number): number[] {
- const h = reinterpretU16AsF16(f16InU16);
- assert(isFiniteF16(h));
- return [f16InU16, ...(isSubnormalNumberF16(h) ? f16ZerosInU16 : [])];
-}
-
-/**
- * @returns the expectation for a single 32bit scalar bitcasted from given pair of
- * f16, result in ExpectionFor32BitsScalarFromF16x2.
- */
-function possible32BitScalarIntervalsFromF16x2(
- f16x2InU16x2: number[],
- type: 'i32' | 'u32' | 'f32'
-): ExpectionFor32BitsScalarFromF16x2 {
- assert(f16x2InU16x2.length === 2);
- let reinterpretFromU32: (x: number) => number;
- let expectationsForValue: (x: number) => Scalar[] | FPInterval[];
- let unboundedExpectations: FPInterval[] | Scalar[];
- if (type === 'u32') {
- reinterpretFromU32 = (x: number) => x;
- expectationsForValue = x => [u32(x)];
- // Scalar expectation can not express "unbounded" for i32 and u32, so use 0 here as a
- // placeholder, and the possibleExpectations should be ignored if the result is unbounded.
- unboundedExpectations = [u32(0)];
- } else if (type === 'i32') {
- reinterpretFromU32 = (x: number) => reinterpretU32AsI32(x);
- expectationsForValue = x => [i32(x)];
- // Scalar expectation can not express "unbounded" for i32 and u32, so use 0 here as a
- // placeholder, and the possibleExpectations should be ignored if the result is unbounded.
- unboundedExpectations = [i32(0)];
- } else {
- assert(type === 'f32');
- reinterpretFromU32 = (x: number) => reinterpretU32AsF32(x);
- expectationsForValue = x => {
- // Handle the possible Inf/NaN/zeros and subnormal cases for f32 result.
- if (!isFiniteF32(x)) {
- return [f32UnboundedInterval];
- }
- // If the casted f16 value is +/-0.0, the result can be one of both. Note that in JS -0.0 === 0.0.
- if (x === 0.0) {
- return [f32ZerosInterval];
- }
- const exactInterval = FP.f32.toInterval(x);
- // If the casted f16 value is subnormal, it also may be flushed to +/-0.0.
- return [exactInterval, ...(isSubnormalNumberF32(x) ? [f32ZerosInterval] : [])];
- };
- unboundedExpectations = [f32UnboundedInterval];
- }
- // Return unbounded expection if f16 Inf/NaN occurs
- if (
- !isFiniteF16(reinterpretU16AsF16(f16x2InU16x2[0])) ||
- !isFiniteF16(reinterpretU16AsF16(f16x2InU16x2[1]))
- ) {
- return { possibleExpectations: unboundedExpectations, isUnbounded: true };
- }
- const possibleU16Bits = f16x2InU16x2.map(possibleBitsInU16FromFiniteF16InU16);
- const possibleExpectations = cartesianProduct(...possibleU16Bits).flatMap<Scalar | FPInterval>(
- (possibleBitsU16x2: readonly number[]) => {
- assert(possibleBitsU16x2.length === 2);
- return expectationsForValue(reinterpretFromU32(u16x2ToU32(possibleBitsU16x2)));
- }
- );
- return { possibleExpectations, isUnbounded: false };
-}
-
-/**
- * @returns a Comparator for checking if a u32 value is a valid
- * bitcast conversion from vec2 f16.
- */
-function bitcastVec2F16ToU32Comparator(vec2F16InU16x2: number[]): Comparator {
- assert(vec2F16InU16x2.length === 2);
- const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'u32');
- // Return alwaysPass if result is expected unbounded.
- if (expectations.isUnbounded) {
- return anyU32;
- }
- return anyOf(...expectations.possibleExpectations);
-}
-
-/**
- * @returns a Comparator for checking if a i32 value is a valid
- * bitcast conversion from vec2 f16.
- */
-function bitcastVec2F16ToI32Comparator(vec2F16InU16x2: number[]): Comparator {
- assert(vec2F16InU16x2.length === 2);
- const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'i32');
- // Return alwaysPass if result is expected unbounded.
- if (expectations.isUnbounded) {
- return anyI32;
- }
- return anyOf(...expectations.possibleExpectations);
-}
-
-/**
- * @returns a Comparator for checking if a i32 value is a valid
- * bitcast conversion from vec2 f16.
- */
-function bitcastVec2F16ToF32Comparator(vec2F16InU16x2: number[]): Comparator {
- assert(vec2F16InU16x2.length === 2);
- const expectations = possible32BitScalarIntervalsFromF16x2(vec2F16InU16x2, 'f32');
- // Return alwaysPass if result is expected unbounded.
- if (expectations.isUnbounded) {
- return anyF32;
- }
- return anyOf(...expectations.possibleExpectations);
-}
-
-/**
- * @returns a Comparator for checking if a vec2 u32 value is a valid
- * bitcast conversion from vec4 f16.
- */
-function bitcastVec4F16ToVec2U32Comparator(vec4F16InU16x4: number[]): Comparator {
- assert(vec4F16InU16x4.length === 4);
- const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e =>
- possible32BitScalarIntervalsFromF16x2(e, 'u32')
- );
- // Return alwaysPass if any element is expected unbounded. Although it may be only one unbounded
- // element in the result vector, currently we don't have a way to build a comparator that expect
- // only one element of i32/u32 vector unbounded.
- if (expectationsPerElement.map(e => e.isUnbounded).reduce((a, b) => a || b, false)) {
- return alwaysPass('any vec2<u32>');
- }
- return anyOf(
- ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map(
- e => new Vector(e as Scalar[])
- )
- );
-}
-
-/**
- * @returns a Comparator for checking if a vec2 i32 value is a valid
- * bitcast conversion from vec4 f16.
- */
-function bitcastVec4F16ToVec2I32Comparator(vec4F16InU16x4: number[]): Comparator {
- assert(vec4F16InU16x4.length === 4);
- const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e =>
- possible32BitScalarIntervalsFromF16x2(e, 'i32')
- );
- // Return alwaysPass if any element is expected unbounded. Although it may be only one unbounded
- // element in the result vector, currently we don't have a way to build a comparator that expect
- // only one element of i32/u32 vector unbounded.
- if (expectationsPerElement.map(e => e.isUnbounded).reduce((a, b) => a || b, false)) {
- return alwaysPass('any vec2<i32>');
- }
- return anyOf(
- ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map(
- e => new Vector(e as Scalar[])
- )
- );
-}
-
-/**
- * @returns a Comparator for checking if a vec2 f32 value is a valid
- * bitcast conversion from vec4 f16.
- */
-function bitcastVec4F16ToVec2F32Comparator(vec4F16InU16x4: number[]): Comparator {
- assert(vec4F16InU16x4.length === 4);
- const expectationsPerElement = [vec4F16InU16x4.slice(0, 2), vec4F16InU16x4.slice(2, 4)].map(e =>
- possible32BitScalarIntervalsFromF16x2(e, 'f32')
- );
- return anyOf(
- ...cartesianProduct(...expectationsPerElement.map(e => e.possibleExpectations)).map(e => [
- e[0] as FPInterval,
- e[1] as FPInterval,
- ])
- );
-}
-
-export const d = makeCaseCache('bitcast', {
- // Identity Cases
- i32_to_i32: () => fullI32Range().map(e => ({ input: i32(e), expected: i32(e) })),
- u32_to_u32: () => fullU32Range().map(e => ({ input: u32(e), expected: u32(e) })),
- f32_inf_nan_to_f32: () =>
- f32RangeWithInfAndNaN.map(e => ({
- input: f32(e),
- expected: bitcastF32ToF32Comparator(e),
- })),
- f32_to_f32: () =>
- f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToF32Comparator(e) })),
- f16_inf_nan_to_f16: () =>
- [...f16FiniteInF16, ...f16InfAndNaNInF16].map(e => ({
- input: f16(e),
- expected: bitcastF16ToF16Comparator(e),
- })),
- f16_to_f16: () =>
- f16FiniteInF16.map(e => ({ input: f16(e), expected: bitcastF16ToF16Comparator(e) })),
-
- // i32,u32,f32 to different i32,u32,f32
- i32_to_u32: () => fullI32Range().map(e => ({ input: i32(e), expected: u32(e) })),
- i32_to_f32: () =>
- i32RangeForF32Finite.map(e => ({
- input: i32(e),
- expected: bitcastI32ToF32Comparator(e),
- })),
- i32_to_f32_inf_nan: () =>
- i32RangeForF32FiniteInfNaN.map(e => ({
- input: i32(e),
- expected: bitcastI32ToF32Comparator(e),
- })),
- u32_to_i32: () => fullU32Range().map(e => ({ input: u32(e), expected: i32(e) })),
- u32_to_f32: () =>
- u32RangeForF32Finite.map(e => ({
- input: u32(e),
- expected: bitcastU32ToF32Comparator(e),
- })),
- u32_to_f32_inf_nan: () =>
- u32RangeForF32FiniteInfNaN.map(e => ({
- input: u32(e),
- expected: bitcastU32ToF32Comparator(e),
- })),
- f32_inf_nan_to_i32: () =>
- f32RangeWithInfAndNaN.map(e => ({
- input: f32(e),
- expected: bitcastF32ToI32Comparator(e),
- })),
- f32_to_i32: () =>
- f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToI32Comparator(e) })),
-
- f32_inf_nan_to_u32: () =>
- f32RangeWithInfAndNaN.map(e => ({
- input: f32(e),
- expected: bitcastF32ToU32Comparator(e),
- })),
- f32_to_u32: () =>
- f32FiniteRange.map(e => ({ input: f32(e), expected: bitcastF32ToU32Comparator(e) })),
-
- // i32,u32,f32 to vec2<f16>
- u32_to_vec2_f16_inf_nan: () =>
- u32RangeForF16Vec2FiniteInfNaN.map(e => ({
- input: u32(e),
- expected: bitcastU32ToVec2F16Comparator(e),
- })),
- u32_to_vec2_f16: () =>
- u32RangeForF16Vec2Finite.map(e => ({
- input: u32(e),
- expected: bitcastU32ToVec2F16Comparator(e),
- })),
- i32_to_vec2_f16_inf_nan: () =>
- i32RangeForF16Vec2FiniteInfNaN.map(e => ({
- input: i32(e),
- expected: bitcastI32ToVec2F16Comparator(e),
- })),
- i32_to_vec2_f16: () =>
- i32RangeForF16Vec2Finite.map(e => ({
- input: i32(e),
- expected: bitcastI32ToVec2F16Comparator(e),
- })),
- f32_inf_nan_to_vec2_f16_inf_nan: () =>
- f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN.map(e => ({
- input: f32(e),
- expected: bitcastF32ToVec2F16Comparator(e),
- })),
- f32_to_vec2_f16: () =>
- f32FiniteRangeForF16Vec2Finite.map(e => ({
- input: f32(e),
- expected: bitcastF32ToVec2F16Comparator(e),
- })),
-
- // vec2<i32>, vec2<u32>, vec2<f32> to vec4<f16>
- vec2_i32_to_vec4_f16_inf_nan: () =>
- slidingSlice(i32RangeForF16Vec2FiniteInfNaN, 2).map(e => ({
- input: toVector(e, i32),
- expected: bitcastVec2I32ToVec4F16Comparator(e),
- })),
- vec2_i32_to_vec4_f16: () =>
- slidingSlice(i32RangeForF16Vec2Finite, 2).map(e => ({
- input: toVector(e, i32),
- expected: bitcastVec2I32ToVec4F16Comparator(e),
- })),
- vec2_u32_to_vec4_f16_inf_nan: () =>
- slidingSlice(u32RangeForF16Vec2FiniteInfNaN, 2).map(e => ({
- input: toVector(e, u32),
- expected: bitcastVec2U32ToVec4F16Comparator(e),
- })),
- vec2_u32_to_vec4_f16: () =>
- slidingSlice(u32RangeForF16Vec2Finite, 2).map(e => ({
- input: toVector(e, u32),
- expected: bitcastVec2U32ToVec4F16Comparator(e),
- })),
- vec2_f32_inf_nan_to_vec4_f16_inf_nan: () =>
- slidingSlice(f32RangeWithInfAndNaNForF16Vec2FiniteInfNaN, 2).map(e => ({
- input: toVector(e, f32),
- expected: bitcastVec2F32ToVec4F16Comparator(e),
- })),
- vec2_f32_to_vec4_f16: () =>
- slidingSlice(f32FiniteRangeForF16Vec2Finite, 2).map(e => ({
- input: toVector(e, f32),
- expected: bitcastVec2F32ToVec4F16Comparator(e),
- })),
-
- // vec2<f16> to i32, u32, f32
- vec2_f16_to_u32: () =>
- f16Vec2FiniteInU16x2.map(e => ({
- input: u16x2ToVec2F16(e),
- expected: bitcastVec2F16ToU32Comparator(e),
- })),
- vec2_f16_inf_nan_to_u32: () =>
- f16Vec2FiniteInfNanInU16x2.map(e => ({
- input: u16x2ToVec2F16(e),
- expected: bitcastVec2F16ToU32Comparator(e),
- })),
- vec2_f16_to_i32: () =>
- f16Vec2FiniteInU16x2.map(e => ({
- input: u16x2ToVec2F16(e),
- expected: bitcastVec2F16ToI32Comparator(e),
- })),
- vec2_f16_inf_nan_to_i32: () =>
- f16Vec2FiniteInfNanInU16x2.map(e => ({
- input: u16x2ToVec2F16(e),
- expected: bitcastVec2F16ToI32Comparator(e),
- })),
- vec2_f16_to_f32_finite: () =>
- f16Vec2FiniteInU16x2
- .filter(u16x2 => isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x2))))
- .map(e => ({
- input: u16x2ToVec2F16(e),
- expected: bitcastVec2F16ToF32Comparator(e),
- })),
- vec2_f16_inf_nan_to_f32: () =>
- f16Vec2FiniteInfNanInU16x2.map(e => ({
- input: u16x2ToVec2F16(e),
- expected: bitcastVec2F16ToF32Comparator(e),
- })),
-
- // vec4<f16> to vec2 of i32, u32, f32
- vec4_f16_to_vec2_u32: () =>
- f16Vec2FiniteInU16x4.map(e => ({
- input: u16x4ToVec4F16(e),
- expected: bitcastVec4F16ToVec2U32Comparator(e),
- })),
- vec4_f16_inf_nan_to_vec2_u32: () =>
- f16Vec2FiniteInfNanInU16x4.map(e => ({
- input: u16x4ToVec4F16(e),
- expected: bitcastVec4F16ToVec2U32Comparator(e),
- })),
- vec4_f16_to_vec2_i32: () =>
- f16Vec2FiniteInU16x4.map(e => ({
- input: u16x4ToVec4F16(e),
- expected: bitcastVec4F16ToVec2I32Comparator(e),
- })),
- vec4_f16_inf_nan_to_vec2_i32: () =>
- f16Vec2FiniteInfNanInU16x4.map(e => ({
- input: u16x4ToVec4F16(e),
- expected: bitcastVec4F16ToVec2I32Comparator(e),
- })),
- vec4_f16_to_vec2_f32_finite: () =>
- f16Vec2FiniteInU16x4
- .filter(
- u16x4 =>
- isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x4.slice(0, 2)))) &&
- isFiniteF32(reinterpretU32AsF32(u16x2ToU32(u16x4.slice(2, 4))))
- )
- .map(e => ({
- input: u16x4ToVec4F16(e),
- expected: bitcastVec4F16ToVec2F32Comparator(e),
- })),
- vec4_f16_inf_nan_to_vec2_f32: () =>
- f16Vec2FiniteInfNanInU16x4.map(e => ({
- input: u16x4ToVec4F16(e),
- expected: bitcastVec4F16ToVec2F32Comparator(e),
- })),
-});
-
/**
* @returns a ShaderBuilder that generates a call to bitcast,
* using appropriate destination type, which optionally can be
@@ -865,7 +71,7 @@ g.test('i32_to_i32')
)
.fn(async t => {
const cases = await d.get('i32_to_i32');
- await run(t, bitcastBuilder('i32', t.params), [TypeI32], TypeI32, t.params, cases);
+ await run(t, bitcastBuilder('i32', t.params), [Type.i32], Type.i32, t.params, cases);
});
g.test('u32_to_u32')
@@ -879,7 +85,7 @@ g.test('u32_to_u32')
)
.fn(async t => {
const cases = await d.get('u32_to_u32');
- await run(t, bitcastBuilder('u32', t.params), [TypeU32], TypeU32, t.params, cases);
+ await run(t, bitcastBuilder('u32', t.params), [Type.u32], Type.u32, t.params, cases);
});
g.test('f32_to_f32')
@@ -896,7 +102,7 @@ g.test('f32_to_f32')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'f32_to_f32' : 'f32_inf_nan_to_f32'
);
- await run(t, bitcastBuilder('f32', t.params), [TypeF32], TypeF32, t.params, cases);
+ await run(t, bitcastBuilder('f32', t.params), [Type.f32], Type.f32, t.params, cases);
});
// To i32 from u32, f32
@@ -911,7 +117,7 @@ g.test('u32_to_i32')
)
.fn(async t => {
const cases = await d.get('u32_to_i32');
- await run(t, bitcastBuilder('i32', t.params), [TypeU32], TypeI32, t.params, cases);
+ await run(t, bitcastBuilder('i32', t.params), [Type.u32], Type.i32, t.params, cases);
});
g.test('f32_to_i32')
@@ -928,7 +134,7 @@ g.test('f32_to_i32')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'f32_to_i32' : 'f32_inf_nan_to_i32'
);
- await run(t, bitcastBuilder('i32', t.params), [TypeF32], TypeI32, t.params, cases);
+ await run(t, bitcastBuilder('i32', t.params), [Type.f32], Type.i32, t.params, cases);
});
// To u32 from i32, f32
@@ -943,7 +149,7 @@ g.test('i32_to_u32')
)
.fn(async t => {
const cases = await d.get('i32_to_u32');
- await run(t, bitcastBuilder('u32', t.params), [TypeI32], TypeU32, t.params, cases);
+ await run(t, bitcastBuilder('u32', t.params), [Type.i32], Type.u32, t.params, cases);
});
g.test('f32_to_u32')
@@ -960,7 +166,7 @@ g.test('f32_to_u32')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'f32_to_u32' : 'f32_inf_nan_to_u32'
);
- await run(t, bitcastBuilder('u32', t.params), [TypeF32], TypeU32, t.params, cases);
+ await run(t, bitcastBuilder('u32', t.params), [Type.f32], Type.u32, t.params, cases);
});
// To f32 from i32, u32
@@ -978,7 +184,7 @@ g.test('i32_to_f32')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'i32_to_f32' : 'i32_to_f32_inf_nan'
);
- await run(t, bitcastBuilder('f32', t.params), [TypeI32], TypeF32, t.params, cases);
+ await run(t, bitcastBuilder('f32', t.params), [Type.i32], Type.f32, t.params, cases);
});
g.test('u32_to_f32')
@@ -995,7 +201,7 @@ g.test('u32_to_f32')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'u32_to_f32' : 'u32_to_f32_inf_nan'
);
- await run(t, bitcastBuilder('f32', t.params), [TypeU32], TypeF32, t.params, cases);
+ await run(t, bitcastBuilder('f32', t.params), [Type.u32], Type.f32, t.params, cases);
});
// 16 bit types
@@ -1020,7 +226,7 @@ g.test('f16_to_f16')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'f16_to_f16' : 'f16_inf_nan_to_f16'
);
- await run(t, bitcastBuilder('f16', t.params), [TypeF16], TypeF16, t.params, cases);
+ await run(t, bitcastBuilder('f16', t.params), [Type.f16], Type.f16, t.params, cases);
});
// f16: 32-bit scalar numeric to vec2<f16>
@@ -1036,14 +242,7 @@ g.test('i32_to_vec2h')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'i32_to_vec2_f16' : 'i32_to_vec2_f16_inf_nan'
);
- await run(
- t,
- bitcastBuilder('vec2<f16>', t.params),
- [TypeI32],
- TypeVec(2, TypeF16),
- t.params,
- cases
- );
+ await run(t, bitcastBuilder('vec2<f16>', t.params), [Type.i32], Type.vec2h, t.params, cases);
});
g.test('u32_to_vec2h')
@@ -1058,14 +257,7 @@ g.test('u32_to_vec2h')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'u32_to_vec2_f16' : 'u32_to_vec2_f16_inf_nan'
);
- await run(
- t,
- bitcastBuilder('vec2<f16>', t.params),
- [TypeU32],
- TypeVec(2, TypeF16),
- t.params,
- cases
- );
+ await run(t, bitcastBuilder('vec2<f16>', t.params), [Type.u32], Type.vec2h, t.params, cases);
});
g.test('f32_to_vec2h')
@@ -1080,14 +272,7 @@ g.test('f32_to_vec2h')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'f32_to_vec2_f16' : 'f32_inf_nan_to_vec2_f16_inf_nan'
);
- await run(
- t,
- bitcastBuilder('vec2<f16>', t.params),
- [TypeF32],
- TypeVec(2, TypeF16),
- t.params,
- cases
- );
+ await run(t, bitcastBuilder('vec2<f16>', t.params), [Type.f32], Type.vec2h, t.params, cases);
});
// f16: vec2<32-bit scalar numeric> to vec4<f16>
@@ -1103,14 +288,7 @@ g.test('vec2i_to_vec4h')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'vec2_i32_to_vec4_f16' : 'vec2_i32_to_vec4_f16_inf_nan'
);
- await run(
- t,
- bitcastBuilder('vec4<f16>', t.params),
- [TypeVec(2, TypeI32)],
- TypeVec(4, TypeF16),
- t.params,
- cases
- );
+ await run(t, bitcastBuilder('vec4<f16>', t.params), [Type.vec2i], Type.vec4h, t.params, cases);
});
g.test('vec2u_to_vec4h')
@@ -1125,14 +303,7 @@ g.test('vec2u_to_vec4h')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'vec2_u32_to_vec4_f16' : 'vec2_u32_to_vec4_f16_inf_nan'
);
- await run(
- t,
- bitcastBuilder('vec4<f16>', t.params),
- [TypeVec(2, TypeU32)],
- TypeVec(4, TypeF16),
- t.params,
- cases
- );
+ await run(t, bitcastBuilder('vec4<f16>', t.params), [Type.vec2u], Type.vec4h, t.params, cases);
});
g.test('vec2f_to_vec4h')
@@ -1149,14 +320,7 @@ g.test('vec2f_to_vec4h')
? 'vec2_f32_to_vec4_f16'
: 'vec2_f32_inf_nan_to_vec4_f16_inf_nan'
);
- await run(
- t,
- bitcastBuilder('vec4<f16>', t.params),
- [TypeVec(2, TypeF32)],
- TypeVec(4, TypeF16),
- t.params,
- cases
- );
+ await run(t, bitcastBuilder('vec4<f16>', t.params), [Type.vec2f], Type.vec4h, t.params, cases);
});
// f16: vec2<f16> to 32-bit scalar numeric
@@ -1172,7 +336,7 @@ g.test('vec2h_to_i32')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'vec2_f16_to_i32' : 'vec2_f16_inf_nan_to_i32'
);
- await run(t, bitcastBuilder('i32', t.params), [TypeVec(2, TypeF16)], TypeI32, t.params, cases);
+ await run(t, bitcastBuilder('i32', t.params), [Type.vec2h], Type.i32, t.params, cases);
});
g.test('vec2h_to_u32')
@@ -1187,7 +351,7 @@ g.test('vec2h_to_u32')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'vec2_f16_to_u32' : 'vec2_f16_inf_nan_to_u32'
);
- await run(t, bitcastBuilder('u32', t.params), [TypeVec(2, TypeF16)], TypeU32, t.params, cases);
+ await run(t, bitcastBuilder('u32', t.params), [Type.vec2h], Type.u32, t.params, cases);
});
g.test('vec2h_to_f32')
@@ -1202,7 +366,7 @@ g.test('vec2h_to_f32')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'vec2_f16_to_f32_finite' : 'vec2_f16_inf_nan_to_f32'
);
- await run(t, bitcastBuilder('f32', t.params), [TypeVec(2, TypeF16)], TypeF32, t.params, cases);
+ await run(t, bitcastBuilder('f32', t.params), [Type.vec2h], Type.f32, t.params, cases);
});
// f16: vec4<f16> to vec2<32-bit scalar numeric>
@@ -1218,14 +382,7 @@ g.test('vec4h_to_vec2i')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'vec4_f16_to_vec2_i32' : 'vec4_f16_inf_nan_to_vec2_i32'
);
- await run(
- t,
- bitcastBuilder('vec2<i32>', t.params),
- [TypeVec(4, TypeF16)],
- TypeVec(2, TypeI32),
- t.params,
- cases
- );
+ await run(t, bitcastBuilder('vec2<i32>', t.params), [Type.vec4h], Type.vec2i, t.params, cases);
});
g.test('vec4h_to_vec2u')
@@ -1240,14 +397,7 @@ g.test('vec4h_to_vec2u')
// Infinities and NaNs are errors in const-eval.
t.params.inputSource === 'const' ? 'vec4_f16_to_vec2_u32' : 'vec4_f16_inf_nan_to_vec2_u32'
);
- await run(
- t,
- bitcastBuilder('vec2<u32>', t.params),
- [TypeVec(4, TypeF16)],
- TypeVec(2, TypeU32),
- t.params,
- cases
- );
+ await run(t, bitcastBuilder('vec2<u32>', t.params), [Type.vec4h], Type.vec2u, t.params, cases);
});
g.test('vec4h_to_vec2f')
@@ -1264,12 +414,230 @@ g.test('vec4h_to_vec2f')
? 'vec4_f16_to_vec2_f32_finite'
: 'vec4_f16_inf_nan_to_vec2_f32'
);
+ await run(t, bitcastBuilder('vec2<f32>', t.params), [Type.vec4h], Type.vec2f, t.params, cases);
+ });
+
+// Abstract Float
+g.test('af_to_f32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast abstract float to f32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = scalarF32Range().map(u => {
+ const res = FP['f32'].addFlushedIfNeeded([u]).map(f => {
+ return f32(f);
+ });
+ return {
+ input: abstractFloat(u),
+ expected: anyOf(...res),
+ };
+ });
+
+ await run(t, bitcastBuilder('f32', t.params), [Type.abstractFloat], Type.f32, t.params, cases);
+ });
+
+g.test('af_to_i32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast abstract float to i32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const values = [
+ 0,
+ 1,
+ 10,
+ 256,
+ u32Bits(0b11111111011111111111111111111111).value,
+ u32Bits(0b11111111010000000000000000000000).value,
+ u32Bits(0b11111110110000000000000000000000).value,
+ u32Bits(0b11111101110000000000000000000000).value,
+ u32Bits(0b11111011110000000000000000000000).value,
+ u32Bits(0b11110111110000000000000000000000).value,
+ u32Bits(0b11101111110000000000000000000000).value,
+ u32Bits(0b11011111110000000000000000000000).value,
+ u32Bits(0b10111111110000000000000000000000).value,
+ u32Bits(0b01111111011111111111111111111111).value,
+ u32Bits(0b01111111010000000000000000000000).value,
+ u32Bits(0b01111110110000000000000000000000).value,
+ u32Bits(0b01111101110000000000000000000000).value,
+ u32Bits(0b01111011110000000000000000000000).value,
+ u32Bits(0b01110111110000000000000000000000).value,
+ u32Bits(0b01101111110000000000000000000000).value,
+ u32Bits(0b01011111110000000000000000000000).value,
+ u32Bits(0b00111111110000000000000000000000).value,
+ ];
+
+ const cases = values.map(u => {
+ return {
+ input: abstractFloat(uint32ToFloat32(u)),
+ expected: i32(u),
+ };
+ });
+
+ await run(t, bitcastBuilder('i32', t.params), [Type.abstractFloat], Type.i32, t.params, cases);
+ });
+
+g.test('af_to_u32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast abstract float to u32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const values = [
+ 0,
+ 1,
+ 10,
+ 256,
+ u32Bits(0b11111111011111111111111111111111).value,
+ u32Bits(0b11111111010000000000000000000000).value,
+ u32Bits(0b11111110110000000000000000000000).value,
+ u32Bits(0b11111101110000000000000000000000).value,
+ u32Bits(0b11111011110000000000000000000000).value,
+ u32Bits(0b11110111110000000000000000000000).value,
+ u32Bits(0b11101111110000000000000000000000).value,
+ u32Bits(0b11011111110000000000000000000000).value,
+ u32Bits(0b10111111110000000000000000000000).value,
+ u32Bits(0b01111111011111111111111111111111).value,
+ u32Bits(0b01111111010000000000000000000000).value,
+ u32Bits(0b01111110110000000000000000000000).value,
+ u32Bits(0b01111101110000000000000000000000).value,
+ u32Bits(0b01111011110000000000000000000000).value,
+ u32Bits(0b01110111110000000000000000000000).value,
+ u32Bits(0b01101111110000000000000000000000).value,
+ u32Bits(0b01011111110000000000000000000000).value,
+ u32Bits(0b00111111110000000000000000000000).value,
+ ];
+
+ const cases = values.map(u => {
+ return {
+ input: abstractFloat(uint32ToFloat32(u)),
+ expected: u32(u),
+ };
+ });
+
+ await run(t, bitcastBuilder('u32', t.params), [Type.abstractFloat], Type.u32, t.params, cases);
+ });
+
+g.test('af_to_vec2f16')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast abstract float to f16 tests`)
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('af_to_vec2_f16');
+
+ await run(
+ t,
+ bitcastBuilder('vec2<f16>', t.params),
+ [Type.abstractFloat],
+ Type.vec2h,
+ t.params,
+ cases
+ );
+ });
+
+g.test('vec2af_to_vec4f16')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast abstract float to f16 tests`)
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('vec2_af_to_vec4_f16');
+
+ await run(
+ t,
+ bitcastBuilder('vec4<f16>', t.params),
+ [Type.vec(2, Type.abstractFloat)],
+ Type.vec4h,
+ t.params,
+ cases
+ );
+ });
+
+// Abstract Int
+g.test('ai_to_i32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast abstract int to i32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('alias', [false, true])
+ )
+ .fn(async t => {
+ const cases = await d.get('ai_to_i32');
+ await run(t, bitcastBuilder('i32', t.params), [Type.abstractInt], Type.i32, t.params, cases);
+ });
+
+g.test('ai_to_u32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast abstract int to u32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('alias', [false, true])
+ )
+ .fn(async t => {
+ const cases = await d.get('ai_to_u32');
+ await run(t, bitcastBuilder('u32', t.params), [Type.abstractInt], Type.u32, t.params, cases);
+ });
+
+g.test('ai_to_f32')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast abstract int to f32 tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('alias', [false, true])
+ )
+ .fn(async t => {
+ const cases = await d.get('ai_to_f32');
+ await run(t, bitcastBuilder('f32', t.params), [Type.abstractInt], Type.f32, t.params, cases);
+ });
+
+g.test('ai_to_vec2h')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast ai to vec2h tests`)
+ .params(u => u.combine('inputSource', onlyConstInputSource).combine('alias', [false, true]))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('ai_to_vec2_f16');
await run(
t,
- bitcastBuilder('vec2<f32>', t.params),
- [TypeVec(4, TypeF16)],
- TypeVec(2, TypeF32),
+ bitcastBuilder('vec2<f16>', t.params),
+ [Type.abstractInt],
+ Type.vec2h,
t.params,
cases
);
});
+
+g.test('vec2ai_to_vec4h')
+ .specURL('https://www.w3.org/TR/WGSL/#bitcast-builtin')
+ .desc(`bitcast vec2ai to vec4h tests`)
+ .params(u => u.combine('inputSource', onlyConstInputSource).combine('alias', [false, true]))
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(async t => {
+ const cases = await d.get('vec2_ai_to_vec4_f16');
+ await run(t, bitcastBuilder('vec4<f16>', t.params), [Type.vec2ai], Type.vec4h, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts
index 282feea703..0afd8c3980 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/builtin.ts
@@ -1,5 +1,6 @@
import {
abstractFloatShaderBuilder,
+ abstractIntShaderBuilder,
basicExpressionBuilder,
basicExpressionWithPredeclarationBuilder,
ShaderBuilder,
@@ -11,10 +12,15 @@ export function builtin(name: string): ShaderBuilder {
}
/* @returns a ShaderBuilder that calls the builtin with the given name that returns AbstractFloats */
-export function abstractBuiltin(name: string): ShaderBuilder {
+export function abstractFloatBuiltin(name: string): ShaderBuilder {
return abstractFloatShaderBuilder(values => `${name}(${values.join(', ')})`);
}
+/* @returns a ShaderBuilder that calls the builtin with the given name that returns AbstractInts */
+export function abstractIntBuiltin(name: string): ShaderBuilder {
+ return abstractIntShaderBuilder(values => `${name}(${values.join(', ')})`);
+}
+
/* @returns a ShaderBuilder that calls the builtin with the given name and has given predeclaration */
export function builtinWithPredeclaration(name: string, predeclaration: string): ShaderBuilder {
return basicExpressionWithPredeclarationBuilder(
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.cache.ts
new file mode 100644
index 0000000000..c0178d9b83
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.cache.ts
@@ -0,0 +1,26 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+const kSmallMagnitudeTestValues = [0.1, 0.9, 1.0, 1.1, 1.9, -0.1, -0.9, -1.0, -1.1, -1.9];
+
+// See https://github.com/gpuweb/cts/issues/2766 for details
+const kIssue2766Value = {
+ f32: 0x8000_0000,
+ f16: 0x8000,
+ abstract: 0x8000_0000_0000_0000,
+};
+
+// Cases: [f32|f16]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ [...kSmallMagnitudeTestValues, kIssue2766Value[trait], ...FP[trait].scalarRange()],
+ 'unfiltered',
+ FP[trait].ceilInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('ceil', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts
index 6cdf90986b..842875f094 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ceil.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'ceil' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn ceil(e: T ) -> T
Returns the ceiling of e. Component-wise when T is a vector.
@@ -10,70 +10,33 @@ Returns the ceiling of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './ceil.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('ceil', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- // Small positive numbers
- 0.1,
- 0.9,
- 1.0,
- 1.1,
- 1.9,
- // Small negative numbers
- -0.1,
- -0.9,
- -1.0,
- -1.1,
- -1.9,
- 0x80000000, // https://github.com/gpuweb/cts/issues/2766
- ...fullF32Range(),
- ],
- 'unfiltered',
- FP.f32.ceilInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- [
- // Small positive numbers
- 0.1,
- 0.9,
- 1.0,
- 1.1,
- 1.9,
- // Small negative numbers
- -0.1,
- -0.9,
- -1.0,
- -1.1,
- -1.9,
- 0x8000, // https://github.com/gpuweb/cts/issues/2766
- ...fullF16Range(),
- ],
- 'unfiltered',
- FP.f16.ceilInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('ceil'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -83,7 +46,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('ceil'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('ceil'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -97,5 +60,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('ceil'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('ceil'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.cache.ts
new file mode 100644
index 0000000000..909d15e7e7
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.cache.ts
@@ -0,0 +1,131 @@
+import { kValue } from '../../../../../util/constants.js';
+import { ScalarType, Type } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { maxBigInt, minBigInt } from '../../../../../util/math.js';
+import { Case } from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+const u32Values = [0, 1, 2, 3, 0x70000000, 0x80000000, kValue.u32.max];
+
+const i32Values = [
+ kValue.i32.negative.min,
+ -3,
+ -2,
+ -1,
+ 0,
+ 1,
+ 2,
+ 3,
+ 0x70000000,
+ kValue.i32.positive.max,
+];
+
+const abstractFloatValues = [
+ kValue.i64.negative.min,
+ -3n,
+ -2n,
+ -1n,
+ 0n,
+ 1n,
+ 2n,
+ 3n,
+ 0x70000000n,
+ kValue.i64.positive.max,
+];
+
+/** @returns a set of clamp test cases from an ascending list of concrete integer values */
+function generateConcreteIntegerTestCases(
+ test_values: Array<number>,
+ type: ScalarType,
+ stage: 'const' | 'non_const'
+): Array<Case> {
+ return test_values.flatMap(low =>
+ test_values.flatMap(high =>
+ stage === 'const' && low > high
+ ? []
+ : test_values.map(e => ({
+ input: [type.create(e), type.create(low), type.create(high)],
+ expected: type.create(Math.min(Math.max(e, low), high)),
+ }))
+ )
+ );
+}
+
+/** @returns a set of clamp test cases from an ascending list of abstract integer values */
+function generateAbstractIntegerTestCases(test_values: Array<bigint>): Array<Case> {
+ return test_values.flatMap(low =>
+ test_values.flatMap(high =>
+ low > high
+ ? []
+ : test_values.map(e => ({
+ input: [
+ Type.abstractInt.create(e),
+ Type.abstractInt.create(low),
+ Type.abstractInt.create(high),
+ ],
+ expected: Type.abstractInt.create(minBigInt(maxBigInt(e, low), high)),
+ }))
+ )
+ );
+}
+
+function generateFloatTestCases(
+ test_values: readonly number[],
+ trait: 'f32' | 'f16' | 'abstract',
+ stage: 'const' | 'non_const'
+): Array<Case> {
+ return test_values.flatMap(low =>
+ test_values.flatMap(high =>
+ stage === 'const' && low > high
+ ? []
+ : test_values.flatMap(e => {
+ const c = FP[trait].makeScalarTripleToIntervalCase(
+ e,
+ low,
+ high,
+ stage === 'const' ? 'finite' : 'unfiltered',
+ ...FP[trait].clampIntervals
+ );
+ return c === undefined ? [] : [c];
+ })
+ )
+ );
+}
+
+// Cases: [f32|f16|abstract]_[non_]const
+// abstract_non_const is empty and unused
+const fp_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return generateFloatTestCases(
+ FP[trait].sparseScalarRange(),
+ trait,
+ nonConst ? 'non_const' : 'const'
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('clamp', {
+ u32_non_const: () => {
+ return generateConcreteIntegerTestCases(u32Values, Type.u32, 'non_const');
+ },
+ u32_const: () => {
+ return generateConcreteIntegerTestCases(u32Values, Type.u32, 'const');
+ },
+ i32_non_const: () => {
+ return generateConcreteIntegerTestCases(i32Values, Type.i32, 'non_const');
+ },
+ i32_const: () => {
+ return generateConcreteIntegerTestCases(i32Values, Type.i32, 'const');
+ },
+ abstract_int: () => {
+ return generateAbstractIntegerTestCases(abstractFloatValues);
+ },
+ ...fp_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts
index 0113fd656f..0b524bccf0 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/clamp.spec.ts
@@ -1,12 +1,12 @@
export const description = `
Execution tests for the 'clamp' builtin function
-S is AbstractInt, i32, or u32
+S is abstract-int, i32, or u32
T is S or vecN<S>
@const fn clamp(e: T , low: T, high: T) -> T
Returns min(max(e,low),high). Component-wise when T is a vector.
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const clamp(e: T , low: T , high: T) -> T
Returns either min(max(e,low),high), or the median of the three values e, low, high.
@@ -15,117 +15,33 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { kValue } from '../../../../../util/constants.js';
-import {
- ScalarType,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeU32,
- TypeAbstractFloat,
-} from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { sparseF32Range, sparseF16Range, sparseF64Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, Case, onlyConstInputSource, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js';
+import { d } from './clamp.cache.js';
export const g = makeTestGroup(GPUTest);
-const u32Values = [0, 1, 2, 3, 0x70000000, 0x80000000, kValue.u32.max];
-
-const i32Values = [
- kValue.i32.negative.min,
- -3,
- -2,
- -1,
- 0,
- 1,
- 2,
- 3,
- 0x70000000,
- kValue.i32.positive.max,
-];
-
-export const d = makeCaseCache('clamp', {
- u32_non_const: () => {
- return generateIntegerTestCases(u32Values, TypeU32, 'non-const');
- },
- u32_const: () => {
- return generateIntegerTestCases(u32Values, TypeU32, 'const');
- },
- i32_non_const: () => {
- return generateIntegerTestCases(i32Values, TypeI32, 'non-const');
- },
- i32_const: () => {
- return generateIntegerTestCases(i32Values, TypeI32, 'const');
- },
- f32_const: () => {
- return generateFloatTestCases(sparseF32Range(), 'f32', 'const');
- },
- f32_non_const: () => {
- return generateFloatTestCases(sparseF32Range(), 'f32', 'non-const');
- },
- f16_const: () => {
- return generateFloatTestCases(sparseF16Range(), 'f16', 'const');
- },
- f16_non_const: () => {
- return generateFloatTestCases(sparseF16Range(), 'f16', 'non-const');
- },
- abstract: () => {
- return generateFloatTestCases(sparseF64Range(), 'abstract', 'const');
- },
-});
-
-/** @returns a set of clamp test cases from an ascending list of integer values */
-function generateIntegerTestCases(
- test_values: Array<number>,
- type: ScalarType,
- stage: 'const' | 'non-const'
-): Array<Case> {
- return test_values.flatMap(low =>
- test_values.flatMap(high =>
- stage === 'const' && low > high
- ? []
- : test_values.map(e => ({
- input: [type.create(e), type.create(low), type.create(high)],
- expected: type.create(Math.min(Math.max(e, low), high)),
- }))
- )
- );
-}
-
-function generateFloatTestCases(
- test_values: readonly number[],
- trait: 'f32' | 'f16' | 'abstract',
- stage: 'const' | 'non-const'
-): Array<Case> {
- return test_values.flatMap(low =>
- test_values.flatMap(high =>
- stage === 'const' && low > high
- ? []
- : test_values.flatMap(e => {
- const c = FP[trait].makeScalarTripleToIntervalCase(
- e,
- low,
- high,
- stage === 'const' ? 'finite' : 'unfiltered',
- ...FP[trait].clampIntervals
- );
- return c === undefined ? [] : [c];
- })
- )
- );
-}
-
g.test('abstract_int')
.specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
.desc(`abstract int tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_int');
+ await run(
+ t,
+ abstractIntBuiltin('clamp'),
+ [Type.abstractInt, Type.abstractInt, Type.abstractInt],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
g.test('u32')
.specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
@@ -135,7 +51,7 @@ g.test('u32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
- await run(t, builtin('clamp'), [TypeU32, TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, builtin('clamp'), [Type.u32, Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('i32')
@@ -146,7 +62,7 @@ g.test('i32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'i32_const' : 'i32_non_const');
- await run(t, builtin('clamp'), [TypeI32, TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, builtin('clamp'), [Type.i32, Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('abstract_float')
@@ -158,12 +74,12 @@ g.test('abstract_float')
.combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
- const cases = await d.get('abstract');
+ const cases = await d.get('abstract_const');
await run(
t,
- abstractBuiltin('clamp'),
- [TypeAbstractFloat, TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBuiltin('clamp'),
+ [Type.abstractFloat, Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -177,7 +93,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('clamp'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('clamp'), [Type.f32, Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -191,5 +107,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('clamp'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('clamp'), [Type.f16, Type.f16, Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.cache.ts
new file mode 100644
index 0000000000..11c9f50a1f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ [
+ // Well-defined accuracy range
+ ...linearRange(-Math.PI, Math.PI, 100),
+ ...FP[trait].scalarRange(),
+ ],
+ trait === 'abstract' ? 'finite' : 'unfiltered',
+ // cos has an absolute accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].cosInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('cos', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts
index 723bca2efd..5675f220bf 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cos.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'cos' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn cos(e: T ) -> T
Returns the cosine of e. Component-wise when T is a vector.
@@ -9,48 +9,33 @@ Returns the cosine of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './cos.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('cos', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- // Well-defined accuracy range
- ...linearRange(-Math.PI, Math.PI, 1000),
- ...fullF32Range(),
- ],
- 'unfiltered',
- FP.f32.cosInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- [
- // Well-defined accuracy range
- ...linearRange(-Math.PI, Math.PI, 1000),
- ...fullF16Range(),
- ],
- 'unfiltered',
- FP.f16.cosInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('cos'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -66,7 +51,7 @@ TODO(#792): Decide what the ground-truth is for these tests. [1]
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('cos'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('cos'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -80,5 +65,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('cos'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('cos'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.cache.ts
new file mode 100644
index 0000000000..0c90f178df
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // cosh has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].coshInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('cosh', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts
index 37fb961c98..d043df9527 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cosh.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'cosh' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn cosh(e: T ) -> T
Returns the hyperbolic cosine of e. Component-wise when T is a vector
@@ -9,38 +9,33 @@ Returns the hyperbolic cosine of e. Component-wise when T is a vector
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './cosh.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('cosh', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.coshInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.coshInterval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.coshInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.coshInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('cosh'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -50,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('cosh'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('cosh'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -64,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('cosh'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('cosh'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts
index cfae4bb6e0..ea0c38ae58 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countLeadingZeros.spec.ts
@@ -12,7 +12,7 @@ Also known as "clz" in some languages.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeU32, u32Bits, u32, TypeI32, i32Bits, i32 } from '../../../../../util/conversion.js';
+import { Type, u32Bits, u32, i32Bits, i32 } from '../../../../../util/conversion.js';
import { allInputSources, Config, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -27,7 +27,7 @@ g.test('u32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('countLeadingZeros'), [TypeU32], TypeU32, cfg, [
+ await run(t, builtin('countLeadingZeros'), [Type.u32], Type.u32, cfg, [
// Zero
{ input: u32Bits(0b00000000000000000000000000000000), expected: u32(32) },
@@ -142,7 +142,7 @@ g.test('i32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('countLeadingZeros'), [TypeI32], TypeI32, cfg, [
+ await run(t, builtin('countLeadingZeros'), [Type.i32], Type.i32, cfg, [
// Zero
{ input: i32Bits(0b00000000000000000000000000000000), expected: i32(32) },
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts
index f0be916285..1937e04283 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countOneBits.spec.ts
@@ -11,7 +11,7 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeU32, u32Bits, u32, TypeI32, i32Bits, i32 } from '../../../../../util/conversion.js';
+import { Type, u32Bits, u32, i32Bits, i32 } from '../../../../../util/conversion.js';
import { allInputSources, Config, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -26,7 +26,7 @@ g.test('u32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('countOneBits'), [TypeU32], TypeU32, cfg, [
+ await run(t, builtin('countOneBits'), [Type.u32], Type.u32, cfg, [
// Zero
{ input: u32Bits(0b00000000000000000000000000000000), expected: u32(0) },
@@ -141,7 +141,7 @@ g.test('i32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('countOneBits'), [TypeI32], TypeI32, cfg, [
+ await run(t, builtin('countOneBits'), [Type.i32], Type.i32, cfg, [
// Zero
{ input: i32Bits(0b00000000000000000000000000000000), expected: i32(0) },
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts
index d0b3198f49..3392a47810 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/countTrailingZeros.spec.ts
@@ -12,7 +12,7 @@ Also known as "ctz" in some languages.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { i32, i32Bits, TypeI32, u32, TypeU32, u32Bits } from '../../../../../util/conversion.js';
+import { i32, i32Bits, Type, u32, u32Bits } from '../../../../../util/conversion.js';
import { allInputSources, Config, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -27,7 +27,7 @@ g.test('u32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('countTrailingZeros'), [TypeU32], TypeU32, cfg, [
+ await run(t, builtin('countTrailingZeros'), [Type.u32], Type.u32, cfg, [
// Zero
{ input: u32Bits(0b00000000000000000000000000000000), expected: u32(32) },
@@ -142,7 +142,7 @@ g.test('i32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('countTrailingZeros'), [TypeI32], TypeI32, cfg, [
+ await run(t, builtin('countTrailingZeros'), [Type.i32], Type.i32, cfg, [
// Zero
{ input: i32Bits(0b00000000000000000000000000000000), expected: i32(32) },
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.cache.ts
new file mode 100644
index 0000000000..396789b4ed
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.cache.ts
@@ -0,0 +1,25 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+// abstract_non_const is empty and not used
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateVectorPairToVectorCases(
+ FP[trait].vectorRange(3),
+ FP[trait].vectorRange(3),
+ nonConst ? 'unfiltered' : 'finite',
+ // cross has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].crossInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('cross', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts
index 2b0b3e58ce..cc32537076 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/cross.spec.ts
@@ -1,77 +1,32 @@
export const description = `
Execution tests for the 'cross' builtin function
-T is AbstractFloat, f32, or f16
+T is abstract-float, f32, or f16
@const fn cross(e1: vec3<T> ,e2: vec3<T>) -> vec3<T>
Returns the cross product of e1 and e2.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeF16, TypeF32, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { sparseVectorF64Range, vectorF16Range, vectorF32Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './cross.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('cross', {
- f32_const: () => {
- return FP.f32.generateVectorPairToVectorCases(
- vectorF32Range(3),
- vectorF32Range(3),
- 'finite',
- FP.f32.crossInterval
- );
- },
- f32_non_const: () => {
- return FP.f32.generateVectorPairToVectorCases(
- vectorF32Range(3),
- vectorF32Range(3),
- 'unfiltered',
- FP.f32.crossInterval
- );
- },
- f16_const: () => {
- return FP.f16.generateVectorPairToVectorCases(
- vectorF16Range(3),
- vectorF16Range(3),
- 'finite',
- FP.f16.crossInterval
- );
- },
- f16_non_const: () => {
- return FP.f16.generateVectorPairToVectorCases(
- vectorF16Range(3),
- vectorF16Range(3),
- 'unfiltered',
- FP.f16.crossInterval
- );
- },
- abstract: () => {
- return FP.abstract.generateVectorPairToVectorCases(
- sparseVectorF64Range(3),
- sparseVectorF64Range(3),
- 'finite',
- FP.abstract.crossInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u => u.combine('inputSource', onlyConstInputSource))
.fn(async t => {
- const cases = await d.get('abstract');
+ const cases = await d.get('abstract_const');
await run(
t,
- abstractBuiltin('cross'),
- [TypeVec(3, TypeAbstractFloat), TypeVec(3, TypeAbstractFloat)],
- TypeVec(3, TypeAbstractFloat),
+ abstractFloatBuiltin('cross'),
+ [Type.vec(3, Type.abstractFloat), Type.vec(3, Type.abstractFloat)],
+ Type.vec(3, Type.abstractFloat),
t.params,
cases
);
@@ -83,14 +38,7 @@ g.test('f32')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(
- t,
- builtin('cross'),
- [TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
- TypeVec(3, TypeF32),
- t.params,
- cases
- );
+ await run(t, builtin('cross'), [Type.vec3f, Type.vec3f], Type.vec3f, t.params, cases);
});
g.test('f16')
@@ -102,12 +50,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(
- t,
- builtin('cross'),
- [TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
- TypeVec(3, TypeF16),
- t.params,
- cases
- );
+ await run(t, builtin('cross'), [Type.vec3h, Type.vec3h], Type.vec3h, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.cache.ts
new file mode 100644
index 0000000000..16abe41f34
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.cache.ts
@@ -0,0 +1,24 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+// abstract_non_const is empty and not used
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // degrees has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].degreesInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('degrees', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts
index f82153ffca..e8589c9933 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/degrees.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'degrees' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<T>
@const fn degrees(e1: T ) -> T
Converts radians to degrees, approximating e1 × 180 ÷ π. Component-wise when T is a vector
@@ -9,46 +9,14 @@ Converts radians to degrees, approximating e1 × 180 ÷ π. Component-wise when
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF16Range, fullF32Range, fullF64Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './degrees.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('degrees', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.degreesInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(
- fullF32Range(),
- 'unfiltered',
- FP.f32.degreesInterval
- );
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.degreesInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(
- fullF16Range(),
- 'unfiltered',
- FP.f16.degreesInterval
- );
- },
- abstract: () => {
- return FP.abstract.generateScalarToIntervalCases(
- fullF64Range(),
- 'finite',
- FP.abstract.degreesInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
@@ -58,12 +26,12 @@ g.test('abstract_float')
.combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
- const cases = await d.get('abstract');
+ const cases = await d.get('abstract_const');
await run(
t,
- abstractBuiltin('degrees'),
- [TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBuiltin('degrees'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -77,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('degrees'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('degrees'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -91,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('degrees'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('degrees'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.cache.ts
new file mode 100644
index 0000000000..ebd414f395
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.cache.ts
@@ -0,0 +1,14 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { sparseScalarF32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+export const d = makeCaseCache('derivatives', {
+ scalar: () => {
+ return FP.f32.generateScalarPairToIntervalCases(
+ sparseScalarF32Range(),
+ sparseScalarF32Range(),
+ 'unfiltered',
+ FP.f32.subtractionInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.ts
new file mode 100644
index 0000000000..2af4a48096
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/derivatives.ts
@@ -0,0 +1,215 @@
+import { GPUTest } from '../../../../../gpu_test.js';
+import { Type, Value } from '../../../../../util/conversion.js';
+import { Case } from '../../case.js';
+import { toComparator } from '../../expectation.js';
+import { packScalarsToVector } from '../../expression.js';
+
+/**
+ * Run a test for a derivative builtin function.
+ * @param t the GPUTest
+ * @param cases list of test cases to run
+ * @param builtin the builtin function to test
+ * @param non_uniform_discard if true, one of each pair of invocations will discard
+ * @param vectorize if defined, the vector width to use (2, 3, or 4)
+ */
+export function runDerivativeTest(
+ t: GPUTest,
+ cases: Case[],
+ builtin: string,
+ non_uniform_discard: boolean,
+ vectorize?: number
+) {
+ // If the 'vectorize' config option was provided, pack the cases into vectors.
+ let type: Type = Type.f32;
+ if (vectorize !== undefined) {
+ const packed = packScalarsToVector([type, type], type, cases, vectorize);
+ cases = packed.cases;
+ type = packed.resultType;
+ }
+
+ ////////////////////////////////////////////////////////////////
+ // The two input values for a given case are distributed to two different invocations in a quad.
+ // We will populate a storage buffer with these input values laid out sequentially:
+ // [ case_0_input_1, case_0_input_0, case_1_input_1, case_1_input_0, ...]
+ //
+ // The render pipeline will be launched several times over a viewport size of (2, 2). Each draw
+ // call will execute a single quad (four fragment invocation), which will exercise two test cases.
+ // Each of these draw calls will use a different instance index, which is forwarded to the
+ // fragment shader. Each invocation will determine its index into the storage buffer using its
+ // fragment position and the instance index for that draw call.
+ //
+ // Consider two draw calls that test 4 cases (c_0, c_1, c_2, c_3).
+ //
+ // For derivatives along the 'x' direction, the mapping from fragment position to case input is:
+ // Quad 0: | c_0_i_1 | c_0_i_0 | Quad 1: | c_2_i_1 | c_2_i_0 |
+ // | c_1_i_1 | c_1_i_0 | | c_3_i_1 | c_3_i_0 |
+ //
+ // For derivatives along the 'y' direction, the mapping from fragment position to case input is:
+ // Quad 0: | c_0_i_1 | c_1_i_1 | Quad 1: | c_2_i_1 | c_3_i_1 |
+ // | c_0_i_0 | c_1_i_0 | | c_2_i_0 | c_3_i_0 |
+ //
+ ////////////////////////////////////////////////////////////////
+
+ // Determine the direction of the derivative ('x' or 'y') from the builtin name.
+ const dir = builtin[3];
+
+ // Determine the WGSL type to use in the shader, and the stride in bytes between values.
+ let valueStride = 4;
+ let wgslType = 'f32';
+ if (vectorize) {
+ wgslType = `vec${vectorize}f`;
+ valueStride = vectorize * 4;
+ if (vectorize === 3) {
+ valueStride = 16;
+ }
+ }
+
+ // Define a vertex shader that draws a triangle over the full viewport, and a fragment shader that
+ // calls the derivative builtin with a value loaded from that fragment's index into the storage
+ // buffer (determined using the quad index and fragment position, as described above).
+ const code = `
+struct CaseInfo {
+ @builtin(position) position: vec4f,
+ @location(0) @interpolate(flat) quad_idx: u32,
+}
+
+@vertex
+fn vert(@builtin(vertex_index) vertex_idx: u32,
+ @builtin(instance_index) instance_idx: u32) -> CaseInfo {
+ const kVertices = array(
+ vec2f(-2, -2),
+ vec2f( 2, -2),
+ vec2f( 0, 2),
+ );
+ return CaseInfo(vec4(kVertices[vertex_idx], 0, 1), instance_idx);
+}
+
+@group(0) @binding(0) var<storage, read> inputs : array<${wgslType}>;
+@group(0) @binding(1) var<storage, read_write> outputs : array<${wgslType}>;
+
+@fragment
+fn frag(info : CaseInfo) {
+ let case_idx = u32(info.position.${dir === 'x' ? 'y' : 'x'});
+ let inv_idx = u32(info.position.${dir});
+ let index = info.quad_idx*4 + case_idx*2 + inv_idx;
+ let input = inputs[index];
+ ${non_uniform_discard ? 'if inv_idx == 0 { discard; }' : ''}
+ outputs[index] = ${builtin}(input);
+}
+`;
+
+ // Create the render pipeline.
+ const module = t.device.createShaderModule({ code });
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module },
+ fragment: { module, targets: [{ format: 'rgba8unorm', writeMask: 0 }] },
+ });
+
+ // Create storage buffers to hold the inputs and outputs.
+ const bufferSize = cases.length * 2 * valueStride;
+ const inputBuffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.STORAGE,
+ mappedAtCreation: true,
+ });
+ const outputBuffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+
+ // Populate the input storage buffer with case input values.
+ const valuesData = new Uint8Array(inputBuffer.getMappedRange());
+ for (let i = 0; i < cases.length; i++) {
+ const inputs = cases[i].input as ReadonlyArray<Value>;
+ inputs[0].copyTo(valuesData, (i * 2 + 1) * valueStride);
+ inputs[1].copyTo(valuesData, i * 2 * valueStride);
+ }
+ inputBuffer.unmap();
+
+ // Create a bind group for the storage buffers.
+ const group = t.device.createBindGroup({
+ entries: [
+ { binding: 0, resource: { buffer: inputBuffer } },
+ { binding: 1, resource: { buffer: outputBuffer } },
+ ],
+ layout: pipeline.getBindGroupLayout(0),
+ });
+
+ // Create a texture to use as a color attachment.
+ // We only need this for launching the desired number of fragment invocations.
+ const colorAttachment = t.device.createTexture({
+ size: { width: 2, height: 2 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+
+ // Submit the render pass to the device.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachment.createView(),
+ loadOp: 'clear',
+ storeOp: 'discard',
+ },
+ ],
+ });
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, group);
+ for (let quad = 0; quad < cases.length / 2; quad++) {
+ pass.draw(3, 1, undefined, quad);
+ }
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Check the outputs match the expected results.
+ t.expectGPUBufferValuesPassCheck(
+ outputBuffer,
+ (outputData: Uint8Array) => {
+ for (let i = 0; i < cases.length; i++) {
+ const c = cases[i];
+
+ // Both invocations involved in the derivative should get the same result.
+ for (let d = 0; d < 2; d++) {
+ if (non_uniform_discard && d === 0) {
+ continue;
+ }
+
+ const index = (i * 2 + d) * valueStride;
+ const result = type.read(outputData, index);
+ const cmp = toComparator(c.expected).compare(result);
+ if (!cmp.matched) {
+ // If this is a coarse derivative, the implementation is also allowed to calculate only
+ // one of the two derivatives and return that result to all of the invocations.
+ if (!builtin.endsWith('Fine')) {
+ const c0 = cases[i % 2 === 0 ? i + 1 : i - 1];
+ const cmp0 = toComparator(c0.expected).compare(result);
+ if (!cmp0.matched) {
+ return new Error(`
+ 1st pair: (${(c.input as Value[]).join(', ')})
+ expected: ${cmp.expected}
+
+ 2nd pair: (${(c0.input as Value[]).join(', ')})
+ expected: ${cmp0.expected}
+
+ returned: ${result}`);
+ }
+ } else {
+ return new Error(`
+ inputs: (${(c.input as Value[]).join(', ')})
+ expected: ${cmp.expected}
+
+ returned: ${result}`);
+ }
+ }
+ }
+ }
+ return undefined;
+ },
+ {
+ type: Uint8Array,
+ typedLength: bufferSize,
+ }
+ );
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.cache.ts
new file mode 100644
index 0000000000..ccd073bce5
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.cache.ts
@@ -0,0 +1,99 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Accuracy for determinant is only defined for e, where e is an integer and
+// |e| < quadroot(2**21) [~38],
+// due to computational complexity of calculating the general solution for 4x4,
+// so custom matrices are used.
+//
+// Note: For 2x2 and 3x3 the limits are squareroot and cuberoot instead of
+// quadroot, but using the tighter 4x4 limits for all cases for simplicity.
+const kDeterminantValues = [-38, -10, -5, -1, 0, 1, 5, 10, 38];
+
+const kDeterminantMatrixValues = {
+ 2: kDeterminantValues.map((f, idx) => [
+ [idx % 4 === 0 ? f : idx, idx % 4 === 1 ? f : -idx],
+ [idx % 4 === 2 ? f : -idx, idx % 4 === 3 ? f : idx],
+ ]),
+ 3: kDeterminantValues.map((f, idx) => [
+ [idx % 9 === 0 ? f : idx, idx % 9 === 1 ? f : -idx, idx % 9 === 2 ? f : idx],
+ [idx % 9 === 3 ? f : -idx, idx % 9 === 4 ? f : idx, idx % 9 === 5 ? f : -idx],
+ [idx % 9 === 6 ? f : idx, idx % 9 === 7 ? f : -idx, idx % 9 === 8 ? f : idx],
+ ]),
+ 4: kDeterminantValues.map((f, idx) => [
+ [
+ idx % 16 === 0 ? f : idx,
+ idx % 16 === 1 ? f : -idx,
+ idx % 16 === 2 ? f : idx,
+ idx % 16 === 3 ? f : -idx,
+ ],
+ [
+ idx % 16 === 4 ? f : -idx,
+ idx % 16 === 5 ? f : idx,
+ idx % 16 === 6 ? f : -idx,
+ idx % 16 === 7 ? f : idx,
+ ],
+ [
+ idx % 16 === 8 ? f : idx,
+ idx % 16 === 9 ? f : -idx,
+ idx % 16 === 10 ? f : idx,
+ idx % 16 === 11 ? f : -idx,
+ ],
+ [
+ idx % 16 === 12 ? f : -idx,
+ idx % 16 === 13 ? f : idx,
+ idx % 16 === 14 ? f : -idx,
+ idx % 16 === 15 ? f : idx,
+ ],
+ ]),
+};
+
+// Cases: f32_matDxD_[non_]const
+const f32_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`f32_mat${dim}x${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateMatrixToScalarCases(
+ kDeterminantMatrixValues[dim],
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.determinantInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: f16_matDxD_[non_]const
+const f16_cases = ([2, 3, 4] as const)
+ .flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`f16_mat${dim}x${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f16.generateMatrixToScalarCases(
+ kDeterminantMatrixValues[dim],
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.determinantInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: abstract_matDxD
+const abstract_cases = ([2, 3, 4] as const)
+ .map(dim => ({
+ [`abstract_mat${dim}x${dim}`]: () => {
+ return FP.abstract.generateMatrixToScalarCases(
+ kDeterminantMatrixValues[dim],
+ 'finite',
+ // determinant has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP.f32.determinantInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('determinant', {
+ ...f32_cases,
+ ...f16_cases,
+ ...abstract_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts
index f08f4f0b6b..638af80aca 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/determinant.spec.ts
@@ -1,109 +1,37 @@
export const description = `
Execution tests for the 'determinant' builtin function
-T is AbstractFloat, f32, or f16
+T is abstract-float, f32, or f16
@const determinant(e: matCxC<T> ) -> T
Returns the determinant of e.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16, TypeMat } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './determinant.cache.js';
export const g = makeTestGroup(GPUTest);
-// Accuracy for determinant is only defined for e, where e is an integer and
-// |e| < quadroot(2**21) [~38],
-// due to computational complexity of calculating the general solution for 4x4,
-// so custom matrices are used.
-//
-// Note: For 2x2 and 3x3 the limits are squareroot and cuberoot instead of
-// quadroot, but using the tighter 4x4 limits for all cases for simplicity.
-const kDeterminantValues = [-38, -10, -5, -1, 0, 1, 5, 10, 38];
-
-const kDeterminantMatrixValues = {
- 2: kDeterminantValues.map((f, idx) => [
- [idx % 4 === 0 ? f : idx, idx % 4 === 1 ? f : -idx],
- [idx % 4 === 2 ? f : -idx, idx % 4 === 3 ? f : idx],
- ]),
- 3: kDeterminantValues.map((f, idx) => [
- [idx % 9 === 0 ? f : idx, idx % 9 === 1 ? f : -idx, idx % 9 === 2 ? f : idx],
- [idx % 9 === 3 ? f : -idx, idx % 9 === 4 ? f : idx, idx % 9 === 5 ? f : -idx],
- [idx % 9 === 6 ? f : idx, idx % 9 === 7 ? f : -idx, idx % 9 === 8 ? f : idx],
- ]),
- 4: kDeterminantValues.map((f, idx) => [
- [
- idx % 16 === 0 ? f : idx,
- idx % 16 === 1 ? f : -idx,
- idx % 16 === 2 ? f : idx,
- idx % 16 === 3 ? f : -idx,
- ],
- [
- idx % 16 === 4 ? f : -idx,
- idx % 16 === 5 ? f : idx,
- idx % 16 === 6 ? f : -idx,
- idx % 16 === 7 ? f : idx,
- ],
- [
- idx % 16 === 8 ? f : idx,
- idx % 16 === 9 ? f : -idx,
- idx % 16 === 10 ? f : idx,
- idx % 16 === 11 ? f : -idx,
- ],
- [
- idx % 16 === 12 ? f : -idx,
- idx % 16 === 13 ? f : idx,
- idx % 16 === 14 ? f : -idx,
- idx % 16 === 15 ? f : idx,
- ],
- ]),
-};
-
-// Cases: f32_matDxD_[non_]const
-const f32_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`f32_mat${dim}x${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateMatrixToScalarCases(
- kDeterminantMatrixValues[dim],
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.determinantInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_matDxD_[non_]const
-const f16_cases = ([2, 3, 4] as const)
- .flatMap(dim =>
- ([true, false] as const).map(nonConst => ({
- [`f16_mat${dim}x${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateMatrixToScalarCases(
- kDeterminantMatrixValues[dim],
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.determinantInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('determinant', {
- ...f32_cases,
- ...f16_cases,
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions')
.desc(`abstract float tests`)
- .params(u => u.combine('inputSource', allInputSources).combine('dimension', [2, 3, 4] as const))
- .unimplemented();
+ .params(u => u.combine('inputSource', onlyConstInputSource).combine('dim', [2, 3, 4] as const))
+ .fn(async t => {
+ const dim = t.params.dim;
+ const cases = await d.get(`abstract_mat${dim}x${dim}`);
+ await run(
+ t,
+ abstractFloatBuiltin('determinant'),
+ [Type.mat(dim, dim, Type.abstractFloat)],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions')
@@ -116,7 +44,7 @@ g.test('f32')
? `f32_mat${dim}x${dim}_const`
: `f32_mat${dim}x${dim}_non_const`
);
- await run(t, builtin('determinant'), [TypeMat(dim, dim, TypeF32)], TypeF32, t.params, cases);
+ await run(t, builtin('determinant'), [Type.mat(dim, dim, Type.f32)], Type.f32, t.params, cases);
});
g.test('f16')
@@ -133,5 +61,5 @@ g.test('f16')
? `f16_mat${dim}x${dim}_const`
: `f16_mat${dim}x${dim}_non_const`
);
- await run(t, builtin('determinant'), [TypeMat(dim, dim, TypeF16)], TypeF16, t.params, cases);
+ await run(t, builtin('determinant'), [Type.mat(dim, dim, Type.f16)], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.cache.ts
new file mode 100644
index 0000000000..0b37190cf7
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.cache.ts
@@ -0,0 +1,49 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const scalar_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarPairToIntervalCases(
+ FP[trait].scalarRange(),
+ FP[trait].scalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // distance has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].distanceInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: [f32|f16|abstract]_vecN_[non_]const
+const vec_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateVectorPairToIntervalCases(
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseVectorRange(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ // distance has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].distanceInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('distance', {
+ ...scalar_cases,
+ ...vec_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts
index 13cddf6403..5f03c49319 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/distance.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'distance' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn distance(e1: T ,e2: T ) -> f32
Returns the distance between e1 and e2 (e.g. length(e1-e2)).
@@ -10,97 +10,77 @@ Returns the distance between e1 and e2 (e.g. length(e1-e2)).
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import {
- fullF32Range,
- fullF16Range,
- sparseVectorF32Range,
- sparseVectorF16Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
-
-import { builtin } from './builtin.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './distance.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: f32_vecN_[non_]const
-const f32_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorPairToIntervalCases(
- sparseVectorF32Range(n),
- sparseVectorF32Range(n),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.distanceInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_vecN_[non_]const
-const f16_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorPairToIntervalCases(
- sparseVectorF16Range(n),
- sparseVectorF16Range(n),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.distanceInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('distance', {
- f32_const: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- fullF32Range(),
- fullF32Range(),
- 'finite',
- FP.f32.distanceInterval
- );
- },
- f32_non_const: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- fullF32Range(),
- fullF32Range(),
- 'unfiltered',
- FP.f32.distanceInterval
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(`abstract float tests`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('distance'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
);
- },
- ...f32_vec_cases,
- f16_const: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- fullF16Range(),
- fullF16Range(),
- 'finite',
- FP.f16.distanceInterval
+ });
+
+g.test('abstract_float_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec2s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_const');
+ await run(
+ t,
+ abstractFloatBuiltin('distance'),
+ [Type.vec2af, Type.vec2af],
+ Type.abstractFloat,
+ t.params,
+ cases
);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- fullF16Range(),
- fullF16Range(),
- 'unfiltered',
- FP.f16.distanceInterval
+ });
+
+g.test('abstract_float_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec3s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_const');
+ await run(
+ t,
+ abstractFloatBuiltin('distance'),
+ [Type.vec3af, Type.vec3af],
+ Type.abstractFloat,
+ t.params,
+ cases
);
- },
- ...f16_vec_cases,
-});
+ });
-g.test('abstract_float')
- .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
- .desc(`abstract float tests`)
- .params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
- )
- .unimplemented();
+g.test('abstract_float_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec4s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_const');
+ await run(
+ t,
+ abstractFloatBuiltin('distance'),
+ [Type.vec4af, Type.vec4af],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
@@ -108,7 +88,7 @@ g.test('f32')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('distance'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('distance'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f32_vec2')
@@ -119,14 +99,7 @@ g.test('f32_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
);
- await run(
- t,
- builtin('distance'),
- [TypeVec(2, TypeF32), TypeVec(2, TypeF32)],
- TypeF32,
- t.params,
- cases
- );
+ await run(t, builtin('distance'), [Type.vec2f, Type.vec2f], Type.f32, t.params, cases);
});
g.test('f32_vec3')
@@ -137,14 +110,7 @@ g.test('f32_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
);
- await run(
- t,
- builtin('distance'),
- [TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
- TypeF32,
- t.params,
- cases
- );
+ await run(t, builtin('distance'), [Type.vec3f, Type.vec3f], Type.f32, t.params, cases);
});
g.test('f32_vec4')
@@ -155,14 +121,7 @@ g.test('f32_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
);
- await run(
- t,
- builtin('distance'),
- [TypeVec(4, TypeF32), TypeVec(4, TypeF32)],
- TypeF32,
- t.params,
- cases
- );
+ await run(t, builtin('distance'), [Type.vec4f, Type.vec4f], Type.f32, t.params, cases);
});
g.test('f16')
@@ -174,7 +133,7 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('distance'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('distance'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('f16_vec2')
@@ -188,14 +147,7 @@ g.test('f16_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
);
- await run(
- t,
- builtin('distance'),
- [TypeVec(2, TypeF16), TypeVec(2, TypeF16)],
- TypeF16,
- t.params,
- cases
- );
+ await run(t, builtin('distance'), [Type.vec2h, Type.vec2h], Type.f16, t.params, cases);
});
g.test('f16_vec3')
@@ -209,14 +161,7 @@ g.test('f16_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
);
- await run(
- t,
- builtin('distance'),
- [TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
- TypeF16,
- t.params,
- cases
- );
+ await run(t, builtin('distance'), [Type.vec3h, Type.vec3h], Type.f16, t.params, cases);
});
g.test('f16_vec4')
@@ -230,12 +175,5 @@ g.test('f16_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
);
- await run(
- t,
- builtin('distance'),
- [TypeVec(4, TypeF16), TypeVec(4, TypeF16)],
- TypeF16,
- t.params,
- cases
- );
+ await run(t, builtin('distance'), [Type.vec4h, Type.vec4h], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.cache.ts
new file mode 100644
index 0000000000..6d1f22def1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.cache.ts
@@ -0,0 +1,118 @@
+import { ROArrayArray } from '../../../../../../common/util/types.js';
+import { assert } from '../../../../../../common/util/util.js';
+import { kValue } from '../../../../../util/constants.js';
+import { FP } from '../../../../../util/floating_point.js';
+import {
+ calculatePermutations,
+ sparseVectorI32Range,
+ sparseVectorI64Range,
+ sparseVectorU32Range,
+ vectorI32Range,
+ vectorI64Range,
+ vectorU32Range,
+} from '../../../../../util/math.js';
+import {
+ generateVectorVectorToI32Cases,
+ generateVectorVectorToI64Cases,
+ generateVectorVectorToU32Cases,
+} from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+function ai_dot(x: bigint[], y: bigint[]): bigint | undefined {
+ assert(x.length === y.length, 'Cannot calculate dot for vectors of different lengths');
+ const multiplications = x.map((_, idx) => x[idx] * y[idx]);
+ if (multiplications.some(kValue.i64.isOOB)) return undefined;
+
+ const result = multiplications.reduce((prev, curr) => prev + curr);
+ if (kValue.i64.isOOB(result)) return undefined;
+
+ // The spec does not state the ordering of summation, so all the
+ // permutations are calculated and the intermediate results checked for
+ // going OOB. vec2 does not need permutations, since a + b === b + a.
+ // All the end results should be the same regardless of the order if the
+ // intermediate additions stay inbounds.
+ if (x.length !== 2) {
+ let wentOOB: boolean = false;
+ const permutations: ROArrayArray<bigint> = calculatePermutations(multiplications);
+ permutations.forEach(p => {
+ if (!wentOOB) {
+ p.reduce((prev, curr) => {
+ const next = prev + curr;
+ if (kValue.i64.isOOB(next)) {
+ wentOOB = true;
+ }
+ return next;
+ });
+ }
+ });
+
+ if (wentOOB) return undefined;
+ }
+
+ return !kValue.i64.isOOB(result) ? result : undefined;
+}
+
+function ci_dot(x: number[], y: number[]): number | undefined {
+ assert(x.length === y.length, 'Cannot calculate dot for vectors of different lengths');
+ return x.reduce((prev, _, idx) => prev + Math.imul(x[idx], y[idx]), 0);
+}
+
+// Cases: [f32|f16|abstract]_vecN_[non_]const
+const float_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(N =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait === 'abstract' ? 'abstract_float' : trait}_vec${N}_${
+ nonConst ? 'non_const' : 'const'
+ }`]: () => {
+ // Emit an empty array for not const abstract float, since they will never be run
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ // vec3 and vec4 require calculating all possible permutations, so their runtime is much
+ // longer per test, so only using sparse vectors for them.
+ return FP[trait].generateVectorPairToIntervalCases(
+ N === 2 ? FP[trait].vectorRange(2) : FP[trait].sparseVectorRange(N),
+ N === 2 ? FP[trait].vectorRange(2) : FP[trait].sparseVectorRange(N),
+ nonConst ? 'unfiltered' : 'finite',
+ // dot has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].dotInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+const cases = {
+ ...float_cases,
+ abstract_int_vec2: () => {
+ return generateVectorVectorToI64Cases(vectorI64Range(2), vectorI64Range(2), ai_dot);
+ },
+ abstract_int_vec3: () => {
+ return generateVectorVectorToI64Cases(sparseVectorI64Range(3), sparseVectorI64Range(3), ai_dot);
+ },
+ abstract_int_vec4: () => {
+ return generateVectorVectorToI64Cases(sparseVectorI64Range(4), sparseVectorI64Range(4), ai_dot);
+ },
+ i32_vec2: () => {
+ return generateVectorVectorToI32Cases(vectorI32Range(2), vectorI32Range(2), ci_dot);
+ },
+ i32_vec3: () => {
+ return generateVectorVectorToI32Cases(sparseVectorI32Range(3), sparseVectorI32Range(3), ci_dot);
+ },
+ i32_vec4: () => {
+ return generateVectorVectorToI32Cases(sparseVectorI32Range(4), sparseVectorI32Range(4), ci_dot);
+ },
+ u32_vec2: () => {
+ return generateVectorVectorToU32Cases(vectorU32Range(2), vectorU32Range(2), ci_dot);
+ },
+ u32_vec3: () => {
+ return generateVectorVectorToU32Cases(sparseVectorU32Range(3), sparseVectorU32Range(3), ci_dot);
+ },
+ u32_vec4: () => {
+ return generateVectorVectorToU32Cases(sparseVectorU32Range(4), sparseVectorU32Range(4), ci_dot);
+ },
+};
+
+export const d = makeCaseCache('dot', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts
index 2726546183..188409bf21 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot.spec.ts
@@ -1,87 +1,182 @@
export const description = `
Execution tests for the 'dot' builtin function
-T is AbstractInt, AbstractFloat, i32, u32, f32, or f16
+T is Type.abstractInt, Type.abstractFloat, i32, u32, f32, or f16
@const fn dot(e1: vecN<T>,e2: vecN<T>) -> T
Returns the dot product of e1 and e2.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { sparseVectorF32Range, vectorF32Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js';
+import { d } from './dot.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: [f32|f16]_vecN_[non_]const
-const cases = (['f32', 'f16'] as const)
- .flatMap(trait =>
- ([2, 3, 4] as const).flatMap(N =>
- ([true, false] as const).map(nonConst => ({
- [`${trait}_vec${N}_${nonConst ? 'non_const' : 'const'}`]: () => {
- // vec3 and vec4 require calculating all possible permutations, so their runtime is much
- // longer per test, so only using sparse vectors for them.
- return FP[trait].generateVectorPairToIntervalCases(
- N === 2 ? vectorF32Range(2) : sparseVectorF32Range(N),
- N === 2 ? vectorF32Range(2) : sparseVectorF32Range(N),
- nonConst ? 'unfiltered' : 'finite',
- FP[trait].dotInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('dot', cases);
-
-g.test('abstract_int')
- .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
- .desc(`abstract int tests`)
+g.test('abstract_int_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`abstract integer tests using vec2s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_int_vec2');
+ await run(
+ t,
+ abstractIntBuiltin('dot'),
+ [Type.vec(2, Type.abstractInt), Type.vec(2, Type.abstractInt)],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_int_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`abstract integer tests using vec3s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_int_vec3');
+ await run(
+ t,
+ abstractIntBuiltin('dot'),
+ [Type.vec(3, Type.abstractInt), Type.vec(3, Type.abstractInt)],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_int_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`abstract integer tests using vec4s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_int_vec4');
+ await run(
+ t,
+ abstractIntBuiltin('dot'),
+ [Type.vec(4, Type.abstractInt), Type.vec(4, Type.abstractInt)],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
+
+g.test('i32_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`i32 tests using vec2s`)
.params(u => u.combine('inputSource', allInputSources))
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('i32_vec2');
+ await run(t, builtin('dot'), [Type.vec2i, Type.vec2i], Type.i32, t.params, cases);
+ });
-g.test('i32')
+g.test('i32_vec3')
.specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
- .desc(`i32 tests`)
+ .desc(`i32 tests using vec3s`)
.params(u => u.combine('inputSource', allInputSources))
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('i32_vec3');
+ await run(t, builtin('dot'), [Type.vec3i, Type.vec3i], Type.i32, t.params, cases);
+ });
-g.test('u32')
+g.test('i32_vec4')
.specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
- .desc(`u32 tests`)
+ .desc(`i32 tests using vec4s`)
.params(u => u.combine('inputSource', allInputSources))
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('i32_vec4');
+ await run(t, builtin('dot'), [Type.vec4i, Type.vec4i], Type.i32, t.params, cases);
+ });
-g.test('abstract_float')
+g.test('u32_vec2')
.specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
- .desc(`abstract float test`)
+ .desc(`u32 tests using vec2s`)
.params(u => u.combine('inputSource', allInputSources))
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('u32_vec2');
+ await run(t, builtin('dot'), [Type.vec2u, Type.vec2u], Type.u32, t.params, cases);
+ });
-g.test('f32_vec2')
+g.test('u32_vec3')
.specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
- .desc(`f32 tests using vec2s`)
+ .desc(`u32 tests using vec3s`)
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
- const cases = await d.get(
- t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
+ const cases = await d.get('u32_vec3');
+ await run(t, builtin('dot'), [Type.vec3u, Type.vec3u], Type.u32, t.params, cases);
+ });
+
+g.test('u32_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`u32 tests using vec4s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get('u32_vec4');
+ await run(t, builtin('dot'), [Type.vec4u, Type.vec4u], Type.u32, t.params, cases);
+ });
+
+g.test('abstract_float_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`abstract float tests using vec2s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_float_vec2_const');
+ await run(
+ t,
+ abstractFloatBuiltin('dot'),
+ [Type.vec(2, Type.abstractFloat), Type.vec(2, Type.abstractFloat)],
+ Type.abstractFloat,
+ t.params,
+ cases
);
+ });
+
+g.test('abstract_float_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`abstract float tests using vec3s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_float_vec3_const');
await run(
t,
- builtin('dot'),
- [TypeVec(2, TypeF32), TypeVec(2, TypeF32)],
- TypeF32,
+ abstractFloatBuiltin('dot'),
+ [Type.vec(3, Type.abstractFloat), Type.vec(3, Type.abstractFloat)],
+ Type.abstractFloat,
t.params,
cases
);
});
+g.test('abstract_float_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`abstract float tests using vec4s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_float_vec4_const');
+ await run(
+ t,
+ abstractFloatBuiltin('dot'),
+ [Type.vec(4, Type.abstractFloat), Type.vec(4, Type.abstractFloat)],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
+
+g.test('f32_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
+ .desc(`f32 tests using vec2s`)
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cases = await d.get(
+ t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
+ );
+ await run(t, builtin('dot'), [Type.vec2f, Type.vec2f], Type.f32, t.params, cases);
+ });
+
g.test('f32_vec3')
.specURL('https://www.w3.org/TR/WGSL/#vector-builtin-functions')
.desc(`f32 tests using vec3s`)
@@ -90,14 +185,7 @@ g.test('f32_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
);
- await run(
- t,
- builtin('dot'),
- [TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
- TypeF32,
- t.params,
- cases
- );
+ await run(t, builtin('dot'), [Type.vec3f, Type.vec3f], Type.f32, t.params, cases);
});
g.test('f32_vec4')
@@ -108,14 +196,7 @@ g.test('f32_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
);
- await run(
- t,
- builtin('dot'),
- [TypeVec(4, TypeF32), TypeVec(4, TypeF32)],
- TypeF32,
- t.params,
- cases
- );
+ await run(t, builtin('dot'), [Type.vec4f, Type.vec4f], Type.f32, t.params, cases);
});
g.test('f16_vec2')
@@ -129,14 +210,7 @@ g.test('f16_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
);
- await run(
- t,
- builtin('dot'),
- [TypeVec(2, TypeF16), TypeVec(2, TypeF16)],
- TypeF16,
- t.params,
- cases
- );
+ await run(t, builtin('dot'), [Type.vec2h, Type.vec2h], Type.f16, t.params, cases);
});
g.test('f16_vec3')
@@ -150,14 +224,7 @@ g.test('f16_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
);
- await run(
- t,
- builtin('dot'),
- [TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
- TypeF16,
- t.params,
- cases
- );
+ await run(t, builtin('dot'), [Type.vec3h, Type.vec3h], Type.f16, t.params, cases);
});
g.test('f16_vec4')
@@ -171,12 +238,5 @@ g.test('f16_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
);
- await run(
- t,
- builtin('dot'),
- [TypeVec(4, TypeF16), TypeVec(4, TypeF16)],
- TypeF16,
- t.params,
- cases
- );
+ await run(t, builtin('dot'), [Type.vec4h, Type.vec4h], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4I8Packed.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4I8Packed.spec.ts
new file mode 100644
index 0000000000..de537c473e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4I8Packed.spec.ts
@@ -0,0 +1,74 @@
+export const description = `
+Execution tests for the 'dot4I8Packed' builtin function
+
+@const fn dot4I8Packed(e1: u32 ,e2: u32) -> i32
+e1 and e2 are interpreted as vectors with four 8-bit signed integer components. Return the signed
+integer dot product of these two vectors. Each component is sign-extended to i32 before performing
+the multiply, and then the add operations are done in WGSL i32 with wrapping behaviour.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { Type, i32, u32 } from '../../../../../util/conversion.js';
+import { Case } from '../../case.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('basic')
+ .specURL('https://www.w3.org/TR/WGSL/#dot4I8Packed-builtin')
+ .desc(
+ `
+@const fn dot4I8Packed(e1: u32, e2: u32) -> i32
+ `
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cfg: Config = t.params;
+
+ const dot4I8Packed = (e1: number, e2: number) => {
+ let result = 0;
+ for (let i = 0; i < 4; ++i) {
+ let e1_i = (e1 >> (i * 8)) & 0xff;
+ if (e1_i >= 128) {
+ e1_i -= 256;
+ }
+ let e2_i = (e2 >> (i * 8)) & 0xff;
+ if (e2_i >= 128) {
+ e2_i -= 256;
+ }
+ result += e1_i * e2_i;
+ }
+ return result;
+ };
+
+ const testInputs = [
+ // dot({0, 0, 0, 0}, {0, 0, 0, 0})
+ [0, 0],
+ // dot({127, 127, 127, 127}, {127, 127, 127, 127})
+ [0x7f7f7f7f, 0x7f7f7f7f],
+ // dot({-128, -128, -128, -128}, {-128, -128, -128, -128})
+ [0x80808080, 0x80808080],
+ // dot({127, 127, 127, 127}, {-128, -128, -128, -128})
+ [0x7f7f7f7f, 0x80808080],
+ // dot({1, 2, 3, 4}, {5, 6, 7, 8})
+ [0x01020304, 0x05060708],
+ // dot({1, 2, 3, 4}, {-1, -2, -3, -4})
+ [0x01020304, 0xfffefdfc],
+ // dot({-5, -6, -7, -8}, {5, 6, 7, 8})
+ [0xfbfaf9f8, 0x05060708],
+ // dot({-9, -10, -11, -12}, {-13, -14, -15, -16})
+ [0xf7f6f5f4, 0xf3f2f1f0],
+ ] as const;
+
+ const makeCase = (x: number, y: number): Case => {
+ return { input: [u32(x), u32(y)], expected: i32(dot4I8Packed(x, y)) };
+ };
+ const cases: Array<Case> = testInputs.flatMap(v => {
+ return [makeCase(...(v as [number, number]))];
+ });
+
+ await run(t, builtin('dot4I8Packed'), [Type.u32, Type.u32], Type.i32, cfg, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4U8Packed.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4U8Packed.spec.ts
new file mode 100644
index 0000000000..a12a3d0123
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dot4U8Packed.spec.ts
@@ -0,0 +1,59 @@
+export const description = `
+Execution tests for the 'dot4U8Packed' builtin function
+
+@const fn dot4U8Packed(e1: u32 ,e2: u32) -> u32
+e1 and e2 are interpreted as vectors with four 8-bit unsigned integer components. Return the
+unsigned integer dot product of these two vectors.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { Type, u32 } from '../../../../../util/conversion.js';
+import { Case } from '../../case.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('basic')
+ .specURL('https://www.w3.org/TR/WGSL/#dot4U8Packed-builtin')
+ .desc(
+ `
+@const fn dot4U8Packed(e1: u32, e2: u32) -> u32
+ `
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cfg: Config = t.params;
+
+ const dot4U8Packed = (e1: number, e2: number) => {
+ let result = 0;
+ for (let i = 0; i < 4; ++i) {
+ const e1_i = (e1 >> (i * 8)) & 0xff;
+ const e2_i = (e2 >> (i * 8)) & 0xff;
+ result += e1_i * e2_i;
+ }
+ return result;
+ };
+
+ const testInputs = [
+ // dot({0, 0, 0, 0}, {0, 0, 0, 0})
+ [0, 0],
+ // dot({255u, 255u, 255u, 255u}, {255u, 255u, 255u, 255u})
+ [0xffffffff, 0xffffffff],
+ // dot({1u, 2u, 3u, 4u}, {5u, 6u, 7u, 8u})
+ [0x01020304, 0x05060708],
+ // dot({120u, 90u, 60u, 30u}, {50u, 100u, 150u, 200u})
+ [0x785a3c1e, 0x326496c8],
+ ] as const;
+
+ const makeCase = (x: number, y: number): Case => {
+ return { input: [u32(x), u32(y)], expected: u32(dot4U8Packed(x, y)) };
+ };
+ const cases: Array<Case> = testInputs.flatMap(v => {
+ return [makeCase(...(v as [number, number]))];
+ });
+
+ await run(t, builtin('dot4U8Packed'), [Type.u32, Type.u32], Type.u32, cfg, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts
index 287a51c699..d922603a9f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdx.spec.ts
@@ -10,14 +10,22 @@ The result is the same as either dpdxFine(e) or dpdxCoarse(e).
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { allInputSources } from '../../expression.js';
+
+import { d } from './derivatives.cache.js';
+import { runDerivativeTest } from './derivatives.js';
export const g = makeTestGroup(GPUTest);
+const builtin = 'dpdx';
+
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
- .desc(`f32 tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('non_uniform_discard', [false, true])
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('scalar');
+ runDerivativeTest(t, cases, builtin, t.params.non_uniform_discard, t.params.vectorize);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts
index 67a75bb010..488f7e5440 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxCoarse.spec.ts
@@ -9,14 +9,22 @@ This may result in fewer unique positions that dpdxFine(e).
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { allInputSources } from '../../expression.js';
+
+import { d } from './derivatives.cache.js';
+import { runDerivativeTest } from './derivatives.js';
export const g = makeTestGroup(GPUTest);
+const builtin = 'dpdxCoarse';
+
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
- .desc(`f32 tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('non_uniform_discard', [false, true])
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('scalar');
+ runDerivativeTest(t, cases, builtin, t.params.non_uniform_discard, t.params.vectorize);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts
index 91d65b990b..180aec2ec4 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdxFine.spec.ts
@@ -8,14 +8,22 @@ Returns the partial derivative of e with respect to window x coordinates.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { allInputSources } from '../../expression.js';
+
+import { d } from './derivatives.cache.js';
+import { runDerivativeTest } from './derivatives.js';
export const g = makeTestGroup(GPUTest);
+const builtin = 'dpdxFine';
+
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
- .desc(`f32 tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('non_uniform_discard', [false, true])
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('scalar');
+ runDerivativeTest(t, cases, builtin, t.params.non_uniform_discard, t.params.vectorize);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts
index 0cd9cafdb9..94df913d5c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdy.spec.ts
@@ -9,14 +9,22 @@ The result is the same as either dpdyFine(e) or dpdyCoarse(e).
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { allInputSources } from '../../expression.js';
+
+import { d } from './derivatives.cache.js';
+import { runDerivativeTest } from './derivatives.js';
export const g = makeTestGroup(GPUTest);
+const builtin = 'dpdy';
+
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
- .desc(`f32 tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('non_uniform_discard', [false, true])
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('scalar');
+ runDerivativeTest(t, cases, builtin, t.params.non_uniform_discard, t.params.vectorize);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts
index f06869fdc2..2974475a6c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyCoarse.spec.ts
@@ -9,14 +9,22 @@ This may result in fewer unique positions that dpdyFine(e).
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { allInputSources } from '../../expression.js';
+
+import { d } from './derivatives.cache.js';
+import { runDerivativeTest } from './derivatives.js';
export const g = makeTestGroup(GPUTest);
+const builtin = 'dpdyCoarse';
+
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
- .desc(`f32 test`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('non_uniform_discard', [false, true])
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('scalar');
+ runDerivativeTest(t, cases, builtin, t.params.non_uniform_discard, t.params.vectorize);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts
index e09761de95..5772024cc6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/dpdyFine.spec.ts
@@ -8,14 +8,22 @@ Returns the partial derivative of e with respect to window y coordinates.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { allInputSources } from '../../expression.js';
+
+import { d } from './derivatives.cache.js';
+import { runDerivativeTest } from './derivatives.js';
export const g = makeTestGroup(GPUTest);
+const builtin = 'dpdyFine';
+
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
- .desc(`f32 tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('non_uniform_discard', [false, true])
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('scalar');
+ runDerivativeTest(t, cases, builtin, t.params.non_uniform_discard, t.params.vectorize);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.cache.ts
new file mode 100644
index 0000000000..5ecc8b2d97
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.cache.ts
@@ -0,0 +1,44 @@
+import { kValue } from '../../../../../util/constants.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// floor(ln(max f32 value)) = 88, so exp(88) will be within range of a f32, but exp(89) will not
+// floor(ln(max f64 value)) = 709, so exp(709) can be handled by the testing framework, but exp(710) will misbehave
+const f32_inputs = [
+ 0, // Returns 1 by definition
+ -89, // Returns subnormal value
+ kValue.f32.negative.min, // Closest to returning 0 as possible
+ ...biasedRange(kValue.f32.negative.max, -88, 100),
+ ...biasedRange(kValue.f32.positive.min, 88, 100),
+ ...linearRange(89, 709, 10), // Overflows f32, but not f64
+];
+
+// floor(ln(max f16 value)) = 11, so exp(11) will be within range of a f16, but exp(12) will not
+const f16_inputs = [
+ 0, // Returns 1 by definition
+ -12, // Returns subnormal value
+ kValue.f16.negative.min, // Closest to returning 0 as possible
+ ...biasedRange(kValue.f16.negative.max, -11, 100),
+ ...biasedRange(kValue.f16.positive.min, 11, 100),
+ ...linearRange(12, 709, 10), // Overflows f16, but not f64
+];
+
+export const d = makeCaseCache('exp', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.expInterval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.expInterval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.expInterval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.expInterval);
+ },
+ abstract: () => {
+ // exp has an ulp accuracy, so is only expected to be as accurate as f32
+ return FP.abstract.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.expInterval);
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts
index 8b1ced3cab..e6bf65fe4f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'exp' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn exp(e1: T ) -> T
Returns the natural exponentiation of e1 (e.g. e^e1). Component-wise when T is a vector.
@@ -9,60 +9,33 @@ Returns the natural exponentiation of e1 (e.g. e^e1). Component-wise when T is a
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { kValue } from '../../../../../util/constants.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { biasedRange, linearRange } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './exp.cache.js';
export const g = makeTestGroup(GPUTest);
-// floor(ln(max f32 value)) = 88, so exp(88) will be within range of a f32, but exp(89) will not
-// floor(ln(max f64 value)) = 709, so exp(709) can be handled by the testing framework, but exp(710) will misbehave
-const f32_inputs = [
- 0, // Returns 1 by definition
- -89, // Returns subnormal value
- kValue.f32.negative.min, // Closest to returning 0 as possible
- ...biasedRange(kValue.f32.negative.max, -88, 100),
- ...biasedRange(kValue.f32.positive.min, 88, 100),
- ...linearRange(89, 709, 10), // Overflows f32, but not f64
-];
-
-// floor(ln(max f16 value)) = 11, so exp(11) will be within range of a f16, but exp(12) will not
-const f16_inputs = [
- 0, // Returns 1 by definition
- -12, // Returns subnormal value
- kValue.f16.negative.min, // Closest to returning 0 as possible
- ...biasedRange(kValue.f16.negative.max, -11, 100),
- ...biasedRange(kValue.f16.positive.min, 11, 100),
- ...linearRange(12, 709, 10), // Overflows f16, but not f64
-];
-
-export const d = makeCaseCache('exp', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.expInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.expInterval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.expInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.expInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('exp'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -72,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('exp'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('exp'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -86,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('exp'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('exp'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.cache.ts
new file mode 100644
index 0000000000..e2d0f1c16c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.cache.ts
@@ -0,0 +1,44 @@
+import { kValue } from '../../../../../util/constants.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// floor(log2(max f32 value)) = 127, so exp2(127) will be within range of a f32, but exp2(128) will not
+// floor(ln(max f64 value)) = 1023, so exp2(1023) can be handled by the testing framework, but exp2(1024) will misbehave
+const f32_inputs = [
+ 0, // Returns 1 by definition
+ -128, // Returns subnormal value
+ kValue.f32.negative.min, // Closest to returning 0 as possible
+ ...biasedRange(kValue.f32.negative.max, -127, 100),
+ ...biasedRange(kValue.f32.positive.min, 127, 100),
+ ...linearRange(128, 1023, 10), // Overflows f32, but not f64
+];
+
+// floor(log2(max f16 value)) = 15, so exp2(15) will be within range of a f16, but exp2(15) will not
+const f16_inputs = [
+ 0, // Returns 1 by definition
+ -16, // Returns subnormal value
+ kValue.f16.negative.min, // Closest to returning 0 as possible
+ ...biasedRange(kValue.f16.negative.max, -15, 100),
+ ...biasedRange(kValue.f16.positive.min, 15, 100),
+ ...linearRange(16, 1023, 10), // Overflows f16, but not f64
+];
+
+export const d = makeCaseCache('exp2', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.exp2Interval);
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.exp2Interval);
+ },
+ f16_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.exp2Interval);
+ },
+ f16_non_const: () => {
+ return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.exp2Interval);
+ },
+ abstract: () => {
+ // exp2 has an ulp accuracy, so is only expected to be as accurate as f32
+ return FP.abstract.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.exp2Interval);
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts
index 67e123cb30..f47d2e5066 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/exp2.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'exp2' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn exp2(e: T ) -> T
Returns 2 raised to the power e (e.g. 2^e). Component-wise when T is a vector.
@@ -9,60 +9,33 @@ Returns 2 raised to the power e (e.g. 2^e). Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { kValue } from '../../../../../util/constants.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { biasedRange, linearRange } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './exp2.cache.js';
export const g = makeTestGroup(GPUTest);
-// floor(log2(max f32 value)) = 127, so exp2(127) will be within range of a f32, but exp2(128) will not
-// floor(ln(max f64 value)) = 1023, so exp2(1023) can be handled by the testing framework, but exp2(1024) will misbehave
-const f32_inputs = [
- 0, // Returns 1 by definition
- -128, // Returns subnormal value
- kValue.f32.negative.min, // Closest to returning 0 as possible
- ...biasedRange(kValue.f32.negative.max, -127, 100),
- ...biasedRange(kValue.f32.positive.min, 127, 100),
- ...linearRange(128, 1023, 10), // Overflows f32, but not f64
-];
-
-// floor(log2(max f16 value)) = 15, so exp2(15) will be within range of a f16, but exp2(15) will not
-const f16_inputs = [
- 0, // Returns 1 by definition
- -16, // Returns subnormal value
- kValue.f16.negative.min, // Closest to returning 0 as possible
- ...biasedRange(kValue.f16.negative.max, -15, 100),
- ...biasedRange(kValue.f16.positive.min, 15, 100),
- ...linearRange(16, 1023, 10), // Overflows f16, but not f64
-];
-
-export const d = makeCaseCache('exp2', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.exp2Interval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.exp2Interval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.exp2Interval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.exp2Interval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('exp2'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -72,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('exp2'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('exp2'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -86,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('exp2'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('exp2'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts
index d535bf5d74..ef04b661bd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/extractBits.spec.ts
@@ -33,17 +33,7 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import {
- i32Bits,
- TypeI32,
- u32,
- TypeU32,
- u32Bits,
- vec2,
- vec3,
- vec4,
- TypeVec,
-} from '../../../../../util/conversion.js';
+import { i32Bits, Type, u32, u32Bits, vec2, vec3, vec4 } from '../../../../../util/conversion.js';
import { allInputSources, Config, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -57,7 +47,7 @@ g.test('u32')
.fn(async t => {
const cfg: Config = t.params;
- const T = t.params.width === 1 ? TypeU32 : TypeVec(t.params.width, TypeU32);
+ const T = t.params.width === 1 ? Type.u32 : Type.vec(t.params.width, Type.u32);
const V = (x: number, y?: number, z?: number, w?: number) => {
y = y === undefined ? x : y;
@@ -193,7 +183,7 @@ g.test('u32')
);
}
- await run(t, builtin('extractBits'), [T, TypeU32, TypeU32], T, cfg, cases);
+ await run(t, builtin('extractBits'), [T, Type.u32, Type.u32], T, cfg, cases);
});
g.test('i32')
@@ -203,7 +193,7 @@ g.test('i32')
.fn(async t => {
const cfg: Config = t.params;
- const T = t.params.width === 1 ? TypeI32 : TypeVec(t.params.width, TypeI32);
+ const T = t.params.width === 1 ? Type.i32 : Type.vec(t.params.width, Type.i32);
const V = (x: number, y?: number, z?: number, w?: number) => {
y = y === undefined ? x : y;
@@ -333,5 +323,5 @@ g.test('i32')
);
}
- await run(t, builtin('extractBits'), [T, TypeU32, TypeU32], T, cfg, cases);
+ await run(t, builtin('extractBits'), [T, Type.u32, Type.u32], T, cfg, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.cache.ts
new file mode 100644
index 0000000000..17a5d0fd8f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.cache.ts
@@ -0,0 +1,125 @@
+import { ROArrayArray } from '../../../../../../common/util/types.js';
+import { anyOf } from '../../../../../util/compare.js';
+import { toVector } from '../../../../../util/conversion.js';
+import { FP, FPKind, FPVector } from '../../../../../util/floating_point.js';
+import { cartesianProduct } from '../../../../../util/math.js';
+import { Case, selectNCases } from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { IntervalFilter } from '../../interval_filter.js';
+
+// Using a bespoke implementation of make*Case and generate*Cases here
+// since faceForwardIntervals is the only builtin with the API signature
+// (vec, vec, vec) -> vec
+//
+// Additionally faceForward has significant complexities around it due to the
+// fact that `dot` is calculated in its operation, but the result of dot isn't
+// used to calculate the builtin's result.
+
+/**
+ * @returns a Case for `faceForward`
+ * @param argumentKind what kind of floating point numbers being operated on
+ * @param parameterKind what kind of floating point operation should be performed,
+ * should be the same as argumentKind, except for abstract
+ * @param x the `x` param for the case
+ * @param y the `y` param for the case
+ * @param z the `z` param for the case
+ * @param check what interval checking to apply
+ * */
+function makeCase(
+ argumentKind: FPKind,
+ parameterKind: FPKind,
+ x: readonly number[],
+ y: readonly number[],
+ z: readonly number[],
+ check: IntervalFilter
+): Case | undefined {
+ const fp = FP[argumentKind];
+ x = x.map(fp.quantize);
+ y = y.map(fp.quantize);
+ z = z.map(fp.quantize);
+
+ const results = FP[parameterKind].faceForwardIntervals(x, y, z);
+ if (check === 'finite' && results.some(r => r === undefined)) {
+ return undefined;
+ }
+
+ // Stripping the undefined results, since undefined is used to signal that an OOB
+ // could occur within the calculation that isn't reflected in the result
+ // intervals.
+ const define_results = results.filter((r): r is FPVector => r !== undefined);
+
+ return {
+ input: [
+ toVector(x, fp.scalarBuilder),
+ toVector(y, fp.scalarBuilder),
+ toVector(z, fp.scalarBuilder),
+ ],
+ expected: anyOf(...define_results),
+ };
+}
+
+/**
+ * @returns an array of Cases for `faceForward`
+ * @param argumentKind what kind of floating point numbers being operated on
+ * @param parameterKind what kind of floating point operation should be performed,
+ * should be the same as argumentKind, except for abstract
+ * @param xs array of inputs to try for the `x` param
+ * @param ys array of inputs to try for the `y` param
+ * @param zs array of inputs to try for the `z` param
+ * @param check what interval checking to apply
+ */
+function generateCases(
+ argumentKind: FPKind,
+ parameterKind: FPKind,
+ xs: ROArrayArray<number>,
+ ys: ROArrayArray<number>,
+ zs: ROArrayArray<number>,
+ check: IntervalFilter
+): Case[] {
+ // Cannot use `cartesianProduct` here due to heterogeneous param types
+ return cartesianProduct(xs, ys, zs)
+ .map(e => makeCase(argumentKind, parameterKind, e[0], e[1], e[2], check))
+ .filter((c): c is Case => c !== undefined);
+}
+
+// Cases: [f32|f16|abstract]_vecN_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ if (trait !== 'abstract') {
+ return generateCases(
+ trait,
+ trait,
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseVectorRange(dim),
+ nonConst ? 'unfiltered' : 'finite'
+ );
+ } else {
+ // Restricting the number of cases, because a vector of abstract floats needs to be returned, which is costly.
+ return selectNCases(
+ 'faceForward',
+ 20,
+ generateCases(
+ trait,
+ // faceForward has an inherited accuracy, so is only expected to be as accurate as f32
+ 'f32',
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseVectorRange(dim),
+ nonConst ? 'unfiltered' : 'finite'
+ )
+ );
+ }
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('faceForward', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts
index 6b6794fb9f..096abe034f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/faceForward.spec.ts
@@ -1,142 +1,68 @@
export const description = `
Execution tests for the 'faceForward' builtin function
-T is vecN<AbstractFloat>, vecN<f32>, or vecN<f16>
+T is vecN<Type.abstractFloat>, vecN<f32>, or vecN<f16>
@const fn faceForward(e1: T ,e2: T ,e3: T ) -> T
Returns e1 if dot(e2,e3) is negative, and -e1 otherwise.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
-import { ROArrayArray } from '../../../../../../common/util/types.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { anyOf } from '../../../../../util/compare.js';
-import { toVector, TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
-import { FP, FPKind, FPVector } from '../../../../../util/floating_point.js';
-import {
- cartesianProduct,
- sparseVectorF32Range,
- sparseVectorF16Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, Case, IntervalFilter, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './faceForward.cache.js';
export const g = makeTestGroup(GPUTest);
-// Using a bespoke implementation of make*Case and generate*Cases here
-// since faceForwardIntervals is the only builtin with the API signature
-// (vec, vec, vec) -> vec
-//
-// Additionally faceForward has significant complexities around it due to the
-// fact that `dot` is calculated in it s operation, but the result of dot isn't
-// used to calculate the builtin's result.
-
-/**
- * @returns a Case for `faceForward`
- * @param kind what kind of floating point numbers being operated on
- * @param x the `x` param for the case
- * @param y the `y` param for the case
- * @param z the `z` param for the case
- * @param check what interval checking to apply
- * */
-function makeCase(
- kind: FPKind,
- x: readonly number[],
- y: readonly number[],
- z: readonly number[],
- check: IntervalFilter
-): Case | undefined {
- const fp = FP[kind];
- x = x.map(fp.quantize);
- y = y.map(fp.quantize);
- z = z.map(fp.quantize);
-
- const results = FP[kind].faceForwardIntervals(x, y, z);
- if (check === 'finite' && results.some(r => r === undefined)) {
- return undefined;
- }
-
- // Stripping the undefined results, since undefined is used to signal that an OOB
- // could occur within the calculation that isn't reflected in the result
- // intervals.
- const define_results = results.filter((r): r is FPVector => r !== undefined);
-
- return {
- input: [
- toVector(x, fp.scalarBuilder),
- toVector(y, fp.scalarBuilder),
- toVector(z, fp.scalarBuilder),
- ],
- expected: anyOf(...define_results),
- };
-}
-
-/**
- * @returns an array of Cases for `faceForward`
- * @param kind what kind of floating point numbers being operated on
- * @param xs array of inputs to try for the `x` param
- * @param ys array of inputs to try for the `y` param
- * @param zs array of inputs to try for the `z` param
- * @param check what interval checking to apply
- */
-function generateCases(
- kind: FPKind,
- xs: ROArrayArray<number>,
- ys: ROArrayArray<number>,
- zs: ROArrayArray<number>,
- check: IntervalFilter
-): Case[] {
- // Cannot use `cartesianProduct` here due to heterogeneous param types
- return cartesianProduct(xs, ys, zs)
- .map(e => makeCase(kind, e[0], e[1], e[2], check))
- .filter((c): c is Case => c !== undefined);
-}
-
-// Cases: f32_vecN_[non_]const
-const f32_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return generateCases(
- 'f32',
- sparseVectorF32Range(n),
- sparseVectorF32Range(n),
- sparseVectorF32Range(n),
- nonConst ? 'unfiltered' : 'finite'
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_vecN_[non_]const
-const f16_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return generateCases(
- 'f16',
- sparseVectorF16Range(n),
- sparseVectorF16Range(n),
- sparseVectorF16Range(n),
- nonConst ? 'unfiltered' : 'finite'
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
+g.test('abstract_float_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec2s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_const');
+ await run(
+ t,
+ abstractFloatBuiltin('faceForward'),
+ [Type.vec2af, Type.vec2af, Type.vec2af],
+ Type.vec2af,
+ t.params,
+ cases
+ );
+ });
-export const d = makeCaseCache('faceForward', {
- ...f32_vec_cases,
- ...f16_vec_cases,
-});
+g.test('abstract_float_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec3s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_const');
+ await run(
+ t,
+ abstractFloatBuiltin('faceForward'),
+ [Type.vec3af, Type.vec3af, Type.vec3af],
+ Type.vec3af,
+ t.params,
+ cases
+ );
+ });
-g.test('abstract_float')
- .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
- .desc(`abstract float tests`)
- .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const))
- .unimplemented();
+g.test('abstract_float_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec4s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_const');
+ await run(
+ t,
+ abstractFloatBuiltin('faceForward'),
+ [Type.vec4af, Type.vec4af, Type.vec4af],
+ Type.vec4af,
+ t.params,
+ cases
+ );
+ });
g.test('f32_vec2')
.specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
@@ -149,8 +75,8 @@ g.test('f32_vec2')
await run(
t,
builtin('faceForward'),
- [TypeVec(2, TypeF32), TypeVec(2, TypeF32), TypeVec(2, TypeF32)],
- TypeVec(2, TypeF32),
+ [Type.vec2f, Type.vec2f, Type.vec2f],
+ Type.vec2f,
t.params,
cases
);
@@ -167,8 +93,8 @@ g.test('f32_vec3')
await run(
t,
builtin('faceForward'),
- [TypeVec(3, TypeF32), TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
- TypeVec(3, TypeF32),
+ [Type.vec3f, Type.vec3f, Type.vec3f],
+ Type.vec3f,
t.params,
cases
);
@@ -185,8 +111,8 @@ g.test('f32_vec4')
await run(
t,
builtin('faceForward'),
- [TypeVec(4, TypeF32), TypeVec(4, TypeF32), TypeVec(4, TypeF32)],
- TypeVec(4, TypeF32),
+ [Type.vec4f, Type.vec4f, Type.vec4f],
+ Type.vec4f,
t.params,
cases
);
@@ -206,8 +132,8 @@ g.test('f16_vec2')
await run(
t,
builtin('faceForward'),
- [TypeVec(2, TypeF16), TypeVec(2, TypeF16), TypeVec(2, TypeF16)],
- TypeVec(2, TypeF16),
+ [Type.vec2h, Type.vec2h, Type.vec2h],
+ Type.vec2h,
t.params,
cases
);
@@ -227,8 +153,8 @@ g.test('f16_vec3')
await run(
t,
builtin('faceForward'),
- [TypeVec(3, TypeF16), TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
- TypeVec(3, TypeF16),
+ [Type.vec3h, Type.vec3h, Type.vec3h],
+ Type.vec3h,
t.params,
cases
);
@@ -248,8 +174,8 @@ g.test('f16_vec4')
await run(
t,
builtin('faceForward'),
- [TypeVec(4, TypeF16), TypeVec(4, TypeF16), TypeVec(4, TypeF16)],
- TypeVec(4, TypeF16),
+ [Type.vec4h, Type.vec4h, Type.vec4h],
+ Type.vec4h,
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts
index 26216563cd..9248b1e2bf 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstLeadingBit.spec.ts
@@ -16,7 +16,7 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { i32, i32Bits, TypeI32, u32, TypeU32, u32Bits } from '../../../../../util/conversion.js';
+import { i32, i32Bits, Type, u32, u32Bits } from '../../../../../util/conversion.js';
import { allInputSources, Config, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -31,7 +31,7 @@ g.test('u32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('firstLeadingBit'), [TypeU32], TypeU32, cfg, [
+ await run(t, builtin('firstLeadingBit'), [Type.u32], Type.u32, cfg, [
// Zero
{ input: u32Bits(0b00000000000000000000000000000000), expected: u32(-1) },
@@ -146,7 +146,7 @@ g.test('i32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('firstLeadingBit'), [TypeI32], TypeI32, cfg, [
+ await run(t, builtin('firstLeadingBit'), [Type.i32], Type.i32, cfg, [
// Zero
{ input: i32Bits(0b00000000000000000000000000000000), expected: i32(-1) },
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts
index 5c65f59d28..a8dd27ee87 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/firstTrailingBit.spec.ts
@@ -12,7 +12,7 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { i32, i32Bits, TypeI32, u32, TypeU32, u32Bits } from '../../../../../util/conversion.js';
+import { i32, i32Bits, Type, u32, u32Bits } from '../../../../../util/conversion.js';
import { allInputSources, Config, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -27,7 +27,7 @@ g.test('u32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('firstTrailingBit'), [TypeU32], TypeU32, cfg, [
+ await run(t, builtin('firstTrailingBit'), [Type.u32], Type.u32, cfg, [
// Zero
{ input: u32Bits(0b00000000000000000000000000000000), expected: u32(-1) },
@@ -142,7 +142,7 @@ g.test('i32')
)
.fn(async t => {
const cfg: Config = t.params;
- await run(t, builtin('firstTrailingBit'), [TypeI32], TypeI32, cfg, [
+ await run(t, builtin('firstTrailingBit'), [Type.i32], Type.i32, cfg, [
// Zero
{ input: i32Bits(0b00000000000000000000000000000000), expected: i32(-1) },
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.cache.ts
new file mode 100644
index 0000000000..0af966cfd2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.cache.ts
@@ -0,0 +1,26 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+const kSmallMagnitudeTestValues = [0.1, 0.9, 1.0, 1.1, 1.9, -0.1, -0.9, -1.0, -1.1, -1.9];
+
+// See https://github.com/gpuweb/cts/issues/2766 for details
+const kIssue2766Value = {
+ abstract: 0x8000_0000_0000_0000,
+ f32: 0x8000_0000,
+ f16: 0x8000,
+};
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ [...kSmallMagnitudeTestValues, kIssue2766Value[trait], ...FP[trait].scalarRange()],
+ 'unfiltered',
+ FP[trait].floorInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('floor', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts
index 873a6772c3..26cffe5d10 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/floor.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'floor' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn floor(e: T ) -> T
Returns the floor of e. Component-wise when T is a vector.
@@ -9,54 +9,14 @@ Returns the floor of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16, TypeAbstractFloat } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range, fullF64Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './floor.cache.js';
export const g = makeTestGroup(GPUTest);
-const kSmallMagnitudeTestValues = [0.1, 0.9, 1.0, 1.1, 1.9, -0.1, -0.9, -1.0, -1.1, -1.9];
-
-export const d = makeCaseCache('floor', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- ...kSmallMagnitudeTestValues,
- ...fullF32Range(),
- 0x8000_0000, // https://github.com/gpuweb/cts/issues/2766
- ],
- 'unfiltered',
- FP.f32.floorInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- [
- ...kSmallMagnitudeTestValues,
- ...fullF16Range(),
- 0x8000, // https://github.com/gpuweb/cts/issues/2766
- ],
- 'unfiltered',
- FP.f16.floorInterval
- );
- },
- abstract: () => {
- return FP.abstract.generateScalarToIntervalCases(
- [
- ...kSmallMagnitudeTestValues,
- ...fullF64Range(),
- 0x8000_0000_0000_0000, // https://github.com/gpuweb/cts/issues/2766
- ],
- 'unfiltered',
- FP.abstract.floorInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
@@ -67,7 +27,14 @@ g.test('abstract_float')
)
.fn(async t => {
const cases = await d.get('abstract');
- await run(t, abstractBuiltin('floor'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+ await run(
+ t,
+ abstractFloatBuiltin('floor'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
});
g.test('f32')
@@ -78,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('floor'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('floor'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -92,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('floor'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('floor'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.cache.ts
new file mode 100644
index 0000000000..20a61ce837
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.cache.ts
@@ -0,0 +1,26 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+// abstract_non_const is empty and not used
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarTripleToIntervalCases(
+ FP[trait].sparseScalarRange(),
+ FP[trait].sparseScalarRange(),
+ FP[trait].sparseScalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // fma has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].fmaInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('fma', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts
index 701f9d7ca9..620792d420 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fma.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'fma' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn fma(e1: T ,e2: T ,e3: T ) -> T
Returns e1 * e2 + e3. Component-wise when T is a vector.
@@ -9,64 +9,14 @@ Returns e1 * e2 + e3. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16, TypeAbstractFloat } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { sparseF32Range, sparseF16Range, sparseF64Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './fma.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('fma', {
- f32_const: () => {
- return FP.f32.generateScalarTripleToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- sparseF32Range(),
- 'finite',
- FP.f32.fmaInterval
- );
- },
- f32_non_const: () => {
- return FP.f32.generateScalarTripleToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- sparseF32Range(),
- 'unfiltered',
- FP.f32.fmaInterval
- );
- },
- f16_const: () => {
- return FP.f16.generateScalarTripleToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- sparseF16Range(),
- 'finite',
- FP.f16.fmaInterval
- );
- },
- f16_non_const: () => {
- return FP.f16.generateScalarTripleToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- sparseF16Range(),
- 'unfiltered',
- FP.f16.fmaInterval
- );
- },
- abstract: () => {
- return FP.abstract.generateScalarTripleToIntervalCases(
- sparseF64Range(),
- sparseF64Range(),
- sparseF64Range(),
- 'finite',
- FP.abstract.fmaInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
@@ -76,12 +26,12 @@ g.test('abstract_float')
.combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
- const cases = await d.get('abstract');
+ const cases = await d.get('abstract_const');
await run(
t,
- abstractBuiltin('fma'),
- [TypeAbstractFloat, TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBuiltin('fma'),
+ [Type.abstractFloat, Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -95,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('fma'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('fma'), [Type.f32, Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -109,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('fma'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('fma'), [Type.f16, Type.f16, Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.cache.ts
new file mode 100644
index 0000000000..5d0933554b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.cache.ts
@@ -0,0 +1,50 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+const kCommonValues = [
+ 0.5, // 0.5 -> 0.5
+ 0.9, // ~0.9 -> ~0.9
+ 1, // 1 -> 0
+ 2, // 2 -> 0
+ 1.11, // ~1.11 -> ~0.11
+ -0.1, // ~-0.1 -> ~0.9
+ -0.5, // -0.5 -> 0.5
+ -0.9, // ~-0.9 -> ~0.1
+ -1, // -1 -> 0
+ -2, // -2 -> 0
+ -1.11, // ~-1.11 -> ~0.89
+];
+
+const kTraitSpecificValues = {
+ f32: [
+ 10.0001, // ~10.0001 -> ~0.0001
+ -10.0001, // -10.0001 -> ~0.9999
+ 0x8000_0000, // https://github.com/gpuweb/cts/issues/2766
+ ],
+ f16: [
+ 10.0078125, // 10.0078125 -> 0.0078125
+ -10.0078125, // -10.0078125 -> 0.9921875
+ 658.5, // 658.5 -> 0.5
+ 0x8000, // https://github.com/gpuweb/cts/issues/2766
+ ],
+ abstract: [
+ 10.0001, // ~10.0001 -> ~0.0001
+ -10.0001, // -10.0001 -> ~0.9999
+ 0x8000_0000, // https://github.com/gpuweb/cts/issues/2766
+ ],
+};
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ [...kCommonValues, ...kTraitSpecificValues[trait], ...FP[trait].scalarRange()],
+ trait === 'abstract' ? 'finite' : 'unfiltered',
+ FP[trait].fractInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('fract', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts
index 44ea31fde2..f1f7279c0b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/fract.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'fract' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn fract(e: T ) -> T
Returns the fractional part of e, computed as e - floor(e).
@@ -10,72 +10,33 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './fract.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('fract', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- 0.5, // 0.5 -> 0.5
- 0.9, // ~0.9 -> ~0.9
- 1, // 1 -> 0
- 2, // 2 -> 0
- 1.11, // ~1.11 -> ~0.11
- 10.0001, // ~10.0001 -> ~0.0001
- -0.1, // ~-0.1 -> ~0.9
- -0.5, // -0.5 -> 0.5
- -0.9, // ~-0.9 -> ~0.1
- -1, // -1 -> 0
- -2, // -2 -> 0
- -1.11, // ~-1.11 -> ~0.89
- -10.0001, // -10.0001 -> ~0.9999
- 0x80000000, // https://github.com/gpuweb/cts/issues/2766
- ...fullF32Range(),
- ],
- 'unfiltered',
- FP.f32.fractInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- [
- 0.5, // 0.5 -> 0.5
- 0.9, // ~0.9 -> ~0.9
- 1, // 1 -> 0
- 2, // 2 -> 0
- 1.11, // ~1.11 -> ~0.11
- 10.0078125, // 10.0078125 -> 0.0078125
- -0.1, // ~-0.1 -> ~0.9
- -0.5, // -0.5 -> 0.5
- -0.9, // ~-0.9 -> ~0.1
- -1, // -1 -> 0
- -2, // -2 -> 0
- -1.11, // ~-1.11 -> ~0.89
- -10.0078125, // -10.0078125 -> 0.9921875
- 658.5, // 658.5 -> 0.5
- ...fullF16Range(),
- ],
- 'unfiltered',
- FP.f16.fractInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('fract'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -85,7 +46,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('fract'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('fract'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -99,5 +60,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('fract'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('fract'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.cache.ts
new file mode 100644
index 0000000000..2211e2adc9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.cache.ts
@@ -0,0 +1,103 @@
+import { skipUndefined } from '../../../../../util/compare.js';
+import {
+ ScalarValue,
+ VectorValue,
+ i32,
+ toVector,
+ abstractInt,
+} from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { frexp } from '../../../../../util/math.js';
+import { Case } from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+/* @returns a fract Case for a given scalar or vector input */
+function makeCaseFract(v: number | readonly number[], trait: 'f32' | 'f16' | 'abstract'): Case {
+ const fp = FP[trait];
+ let toInput: (n: readonly number[]) => ScalarValue | VectorValue;
+ let toOutput: (n: readonly number[]) => ScalarValue | VectorValue;
+ if (v instanceof Array) {
+ // Input is vector
+ toInput = (n: readonly number[]) => toVector(n, fp.scalarBuilder);
+ toOutput = (n: readonly number[]) => toVector(n, fp.scalarBuilder);
+ } else {
+ // Input is scalar, also wrap it in an array.
+ v = [v];
+ toInput = (n: readonly number[]) => fp.scalarBuilder(n[0]);
+ toOutput = (n: readonly number[]) => fp.scalarBuilder(n[0]);
+ }
+
+ v = v.map(fp.quantize);
+ if (v.some(e => e !== 0 && fp.isSubnormal(e))) {
+ return { input: toInput(v), expected: skipUndefined(undefined) };
+ }
+
+ const fs = v.map(e => {
+ return frexp(e, trait !== 'abstract' ? trait : 'f64').fract;
+ });
+
+ return { input: toInput(v), expected: toOutput(fs) };
+}
+
+/* @returns an exp Case for a given scalar or vector input */
+function makeCaseExp(v: number | readonly number[], trait: 'f32' | 'f16' | 'abstract'): Case {
+ const fp = FP[trait];
+ let toInput: (n: readonly number[]) => ScalarValue | VectorValue;
+ let toOutput: (n: readonly number[]) => ScalarValue | VectorValue;
+ if (v instanceof Array) {
+ // Input is vector
+ toInput = (n: readonly number[]) => toVector(n, fp.scalarBuilder);
+ toOutput = (n: readonly number[]) =>
+ toVector(n, trait !== 'abstract' ? i32 : (n: number) => abstractInt(BigInt(n)));
+ } else {
+ // Input is scalar, also wrap it in an array.
+ v = [v];
+ toInput = (n: readonly number[]) => fp.scalarBuilder(n[0]);
+ toOutput = (n: readonly number[]) =>
+ trait !== 'abstract' ? i32(n[0]) : abstractInt(BigInt(n[0]));
+ }
+
+ v = v.map(fp.quantize);
+ if (v.some(e => e !== 0 && fp.isSubnormal(e))) {
+ return { input: toInput(v), expected: skipUndefined(undefined) };
+ }
+
+ const fs = v.map(e => {
+ return frexp(e, trait !== 'abstract' ? trait : 'f64').exp;
+ });
+
+ return { input: toInput(v), expected: toOutput(fs) };
+}
+
+// Cases: [f32|f16]_vecN_[exp|whole]
+const vec_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(dim =>
+ (['exp', 'fract'] as const).map(portion => ({
+ [`${trait}_vec${dim}_${portion}`]: () => {
+ return FP[trait]
+ .vectorRange(dim)
+ .map(v => (portion === 'exp' ? makeCaseExp(v, trait) : makeCaseFract(v, trait)));
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: [f32|f16]_[exp|whole]
+const scalar_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ (['exp', 'fract'] as const).map(portion => ({
+ [`${trait}_${portion}`]: () => {
+ return FP[trait]
+ .scalarRange()
+ .map(v => (portion === 'exp' ? makeCaseExp(v, trait) : makeCaseFract(v, trait)));
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('frexp', {
+ ...scalar_cases,
+ ...vec_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts
index ffe672b08c..8f07f3990d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/frexp.spec.ts
@@ -15,34 +15,19 @@ The magnitude of the significand is in the range of [0.5, 1.0) or 0.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { skipUndefined } from '../../../../../util/compare.js';
-import {
- i32,
- Scalar,
- toVector,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeVec,
- Vector,
-} from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import {
- frexp,
- fullF16Range,
- fullF32Range,
- vectorF16Range,
- vectorF32Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import {
+ ShaderBuilder,
allInputSources,
basicExpressionBuilder,
- Case,
run,
- ShaderBuilder,
+ abstractFloatShaderBuilder,
+ abstractIntShaderBuilder,
+ onlyConstInputSource,
} from '../../expression.js';
+import { d } from './frexp.cache.js';
+
export const g = makeTestGroup(GPUTest);
/* @returns an ShaderBuilder that evaluates frexp and returns .fract from the result structure */
@@ -55,112 +40,159 @@ function expBuilder(): ShaderBuilder {
return basicExpressionBuilder(value => `frexp(${value}).exp`);
}
-/* @returns a fract Case for a given scalar or vector input */
-function makeVectorCaseFract(v: number | readonly number[], trait: 'f32' | 'f16'): Case {
- const fp = FP[trait];
- let toInput: (n: readonly number[]) => Scalar | Vector;
- let toOutput: (n: readonly number[]) => Scalar | Vector;
- if (v instanceof Array) {
- // Input is vector
- toInput = (n: readonly number[]) => toVector(n, fp.scalarBuilder);
- toOutput = (n: readonly number[]) => toVector(n, fp.scalarBuilder);
- } else {
- // Input is scalar, also wrap it in an array.
- v = [v];
- toInput = (n: readonly number[]) => fp.scalarBuilder(n[0]);
- toOutput = (n: readonly number[]) => fp.scalarBuilder(n[0]);
- }
-
- v = v.map(fp.quantize);
- if (v.some(e => e !== 0 && fp.isSubnormal(e))) {
- return { input: toInput(v), expected: skipUndefined(undefined) };
- }
-
- const fs = v.map(e => {
- return frexp(e, trait).fract;
+/* @returns an ShaderBuilder that evaluates frexp and returns .fract from the result structure, for abstract inputs */
+function abstractFractBuilder(): ShaderBuilder {
+ return abstractFloatShaderBuilder(value => `frexp(${value}).fract`);
+}
+
+/* @returns an ShaderBuilder that evaluates frexp and returns .exp from the result structure, for abstract inputs */
+function abstractExpBuilder(): ShaderBuilder {
+ return abstractIntShaderBuilder(value => `frexp(${value}).exp`);
+}
+
+g.test('abstract_float_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is AbstractFloat
+
+struct __frexp_result_abstract {
+ fract : AbstractFloat, // fract part
+ exp : AbstractInt // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_fract');
+ await run(t, abstractFractBuilder(), [Type.abstractFloat], Type.abstractFloat, t.params, cases);
+ });
+
+g.test('abstract_float_exp')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is AbstractFloat
+
+struct __frexp_result_abstract {
+ fract : AbstractFloat, // fract part
+ exp : AbstractInt // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_exp');
+ await run(t, abstractExpBuilder(), [Type.abstractFloat], Type.abstractInt, t.params, cases);
+ });
+
+g.test('abstract_float_vec2_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec2<AbstractFloat>
+
+struct __frexp_result_vec2_abstract {
+ fract : vec2<AbstractFloat>, // fract part
+ exp : vec2<AbstractInt> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_fract');
+ await run(t, abstractFractBuilder(), [Type.vec2af], Type.vec2af, t.params, cases);
+ });
+
+g.test('abstract_float_vec2_exp')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec2<AbstractFloat>
+
+struct __frexp_result_vec2_abstract {
+ fract : vec2<AbstractFloat>, // fractional part
+ exp : vec2<AbstractInt> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_exp');
+ await run(t, abstractExpBuilder(), [Type.vec2af], Type.vec2ai, t.params, cases);
});
- return { input: toInput(v), expected: toOutput(fs) };
+g.test('abstract_float_vec3_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec3<AbstractFloat>
+
+struct __frexp_result_vec3_abstract {
+ fract : vec3<AbstractFloat>, // fractional part
+ exp : vec3<AbstractInt> // exponent part
}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_fract');
+ await run(t, abstractFractBuilder(), [Type.vec3af], Type.vec3af, t.params, cases);
+ });
-/* @returns an exp Case for a given scalar or vector input */
-function makeVectorCaseExp(v: number | readonly number[], trait: 'f32' | 'f16'): Case {
- const fp = FP[trait];
- let toInput: (n: readonly number[]) => Scalar | Vector;
- let toOutput: (n: readonly number[]) => Scalar | Vector;
- if (v instanceof Array) {
- // Input is vector
- toInput = (n: readonly number[]) => toVector(n, fp.scalarBuilder);
- toOutput = (n: readonly number[]) => toVector(n, i32);
- } else {
- // Input is scalar, also wrap it in an array.
- v = [v];
- toInput = (n: readonly number[]) => fp.scalarBuilder(n[0]);
- toOutput = (n: readonly number[]) => i32(n[0]);
- }
-
- v = v.map(fp.quantize);
- if (v.some(e => e !== 0 && fp.isSubnormal(e))) {
- return { input: toInput(v), expected: skipUndefined(undefined) };
- }
-
- const fs = v.map(e => {
- return frexp(e, trait).exp;
+g.test('abstract_float_vec3_exp')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec3<AbstractFloat>
+
+struct __frexp_result_vec3_abstract {
+ fract : vec3<AbstractFloat>, // fractional part
+ exp : vec3<AbstractInt> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_exp');
+ await run(t, abstractExpBuilder(), [Type.vec3af], Type.vec3ai, t.params, cases);
});
- return { input: toInput(v), expected: toOutput(fs) };
+g.test('abstract_float_vec4_fract')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec4<AbstractFloat>
+
+struct __frexp_result_vec4_abstract {
+ fract : vec4<AbstractFloat>, // fractional part
+ exp : vec4<AbstractInt> // exponent part
}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_fract');
+ await run(t, abstractFractBuilder(), [Type.vec4af], Type.vec4af, t.params, cases);
+ });
+
+g.test('abstract_float_vec4_exp')
+ .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
+ .desc(
+ `
+T is vec4<AbstractFloat>
-export const d = makeCaseCache('frexp', {
- f32_fract: () => {
- return fullF32Range().map(v => makeVectorCaseFract(v, 'f32'));
- },
- f32_exp: () => {
- return fullF32Range().map(v => makeVectorCaseExp(v, 'f32'));
- },
- f32_vec2_fract: () => {
- return vectorF32Range(2).map(v => makeVectorCaseFract(v, 'f32'));
- },
- f32_vec2_exp: () => {
- return vectorF32Range(2).map(v => makeVectorCaseExp(v, 'f32'));
- },
- f32_vec3_fract: () => {
- return vectorF32Range(3).map(v => makeVectorCaseFract(v, 'f32'));
- },
- f32_vec3_exp: () => {
- return vectorF32Range(3).map(v => makeVectorCaseExp(v, 'f32'));
- },
- f32_vec4_fract: () => {
- return vectorF32Range(4).map(v => makeVectorCaseFract(v, 'f32'));
- },
- f32_vec4_exp: () => {
- return vectorF32Range(4).map(v => makeVectorCaseExp(v, 'f32'));
- },
- f16_fract: () => {
- return fullF16Range().map(v => makeVectorCaseFract(v, 'f16'));
- },
- f16_exp: () => {
- return fullF16Range().map(v => makeVectorCaseExp(v, 'f16'));
- },
- f16_vec2_fract: () => {
- return vectorF16Range(2).map(v => makeVectorCaseFract(v, 'f16'));
- },
- f16_vec2_exp: () => {
- return vectorF16Range(2).map(v => makeVectorCaseExp(v, 'f16'));
- },
- f16_vec3_fract: () => {
- return vectorF16Range(3).map(v => makeVectorCaseFract(v, 'f16'));
- },
- f16_vec3_exp: () => {
- return vectorF16Range(3).map(v => makeVectorCaseExp(v, 'f16'));
- },
- f16_vec4_fract: () => {
- return vectorF16Range(4).map(v => makeVectorCaseFract(v, 'f16'));
- },
- f16_vec4_exp: () => {
- return vectorF16Range(4).map(v => makeVectorCaseExp(v, 'f16'));
- },
-});
+struct __frexp_result_vec4_abstract {
+ fract : vec4<AbstractFloat>, // fractional part
+ exp : vec4<AbstractInt> // exponent part
+}
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_exp');
+ await run(t, abstractExpBuilder(), [Type.vec4af], Type.vec4ai, t.params, cases);
+ });
g.test('f32_fract')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -177,7 +209,7 @@ struct __frexp_result_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_fract');
- await run(t, fractBuilder(), [TypeF32], TypeF32, t.params, cases);
+ await run(t, fractBuilder(), [Type.f32], Type.f32, t.params, cases);
});
g.test('f32_exp')
@@ -195,7 +227,7 @@ struct __frexp_result_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_exp');
- await run(t, expBuilder(), [TypeF32], TypeI32, t.params, cases);
+ await run(t, expBuilder(), [Type.f32], Type.i32, t.params, cases);
});
g.test('f32_vec2_fract')
@@ -213,7 +245,7 @@ struct __frexp_result_vec2_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec2_fract');
- await run(t, fractBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec2f], Type.vec2f, t.params, cases);
});
g.test('f32_vec2_exp')
@@ -231,7 +263,7 @@ struct __frexp_result_vec2_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec2_exp');
- await run(t, expBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeI32), t.params, cases);
+ await run(t, expBuilder(), [Type.vec2f], Type.vec2i, t.params, cases);
});
g.test('f32_vec3_fract')
@@ -249,7 +281,7 @@ struct __frexp_result_vec3_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec3_fract');
- await run(t, fractBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec3f], Type.vec3f, t.params, cases);
});
g.test('f32_vec3_exp')
@@ -267,7 +299,7 @@ struct __frexp_result_vec3_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec3_exp');
- await run(t, expBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeI32), t.params, cases);
+ await run(t, expBuilder(), [Type.vec3f], Type.vec3i, t.params, cases);
});
g.test('f32_vec4_fract')
@@ -285,7 +317,7 @@ struct __frexp_result_vec4_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec4_fract');
- await run(t, fractBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec4f], Type.vec4f, t.params, cases);
});
g.test('f32_vec4_exp')
@@ -303,7 +335,7 @@ struct __frexp_result_vec4_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec4_exp');
- await run(t, expBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeI32), t.params, cases);
+ await run(t, expBuilder(), [Type.vec4f], Type.vec4i, t.params, cases);
});
g.test('f16_fract')
@@ -324,7 +356,7 @@ struct __frexp_result_f16 {
})
.fn(async t => {
const cases = await d.get('f16_fract');
- await run(t, fractBuilder(), [TypeF16], TypeF16, t.params, cases);
+ await run(t, fractBuilder(), [Type.f16], Type.f16, t.params, cases);
});
g.test('f16_exp')
@@ -345,7 +377,7 @@ struct __frexp_result_f16 {
})
.fn(async t => {
const cases = await d.get('f16_exp');
- await run(t, expBuilder(), [TypeF16], TypeI32, t.params, cases);
+ await run(t, expBuilder(), [Type.f16], Type.i32, t.params, cases);
});
g.test('f16_vec2_fract')
@@ -366,7 +398,7 @@ struct __frexp_result_vec2_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec2_fract');
- await run(t, fractBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec2h], Type.vec2h, t.params, cases);
});
g.test('f16_vec2_exp')
@@ -387,7 +419,7 @@ struct __frexp_result_vec2_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec2_exp');
- await run(t, expBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeI32), t.params, cases);
+ await run(t, expBuilder(), [Type.vec2h], Type.vec2i, t.params, cases);
});
g.test('f16_vec3_fract')
@@ -408,7 +440,7 @@ struct __frexp_result_vec3_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec3_fract');
- await run(t, fractBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec3h], Type.vec3h, t.params, cases);
});
g.test('f16_vec3_exp')
@@ -429,7 +461,7 @@ struct __frexp_result_vec3_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec3_exp');
- await run(t, expBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeI32), t.params, cases);
+ await run(t, expBuilder(), [Type.vec3h], Type.vec3i, t.params, cases);
});
g.test('f16_vec4_fract')
@@ -450,7 +482,7 @@ struct __frexp_result_vec4_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec4_fract');
- await run(t, fractBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec4h], Type.vec4h, t.params, cases);
});
g.test('f16_vec4_exp')
@@ -471,5 +503,5 @@ struct __frexp_result_vec4_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec4_exp');
- await run(t, expBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeI32), t.params, cases);
+ await run(t, expBuilder(), [Type.vec4h], Type.vec4i, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts
index 1068e76252..b3eb65781d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/insertBits.spec.ts
@@ -18,17 +18,7 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import {
- i32Bits,
- TypeI32,
- u32,
- TypeU32,
- u32Bits,
- vec2,
- vec3,
- vec4,
- TypeVec,
-} from '../../../../../util/conversion.js';
+import { i32Bits, Type, u32, u32Bits, vec2, vec3, vec4 } from '../../../../../util/conversion.js';
import { allInputSources, Config, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -46,8 +36,8 @@ g.test('integer')
)
.fn(async t => {
const cfg: Config = t.params;
- const scalarType = t.params.signed ? TypeI32 : TypeU32;
- const T = t.params.width === 1 ? scalarType : TypeVec(t.params.width, scalarType);
+ const scalarType = t.params.signed ? Type.i32 : Type.u32;
+ const T = t.params.width === 1 ? scalarType : Type.vec(t.params.width, scalarType);
const V = (x: number, y?: number, z?: number, w?: number) => {
y = y === undefined ? x : y;
@@ -382,5 +372,5 @@ g.test('integer')
);
}
- await run(t, builtin('insertBits'), [T, T, TypeU32, TypeU32], T, cfg, cases);
+ await run(t, builtin('insertBits'), [T, T, Type.u32, Type.u32], T, cfg, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.cache.ts
new file mode 100644
index 0000000000..e50d6d4866
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.cache.ts
@@ -0,0 +1,44 @@
+import { kValue } from '../../../../../util/constants.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+export const d = makeCaseCache('inverseSqrt', {
+ f32: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ // 0 < x <= 1 linearly spread
+ ...linearRange(kValue.f32.positive.min, 1, 100),
+ // 1 <= x < 2^32, biased towards 1
+ ...biasedRange(1, 2 ** 32, 1000),
+ ],
+ 'unfiltered',
+ FP.f32.inverseSqrtInterval
+ );
+ },
+ f16: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ [
+ // 0 < x <= 1 linearly spread
+ ...linearRange(kValue.f16.positive.min, 1, 100),
+ // 1 <= x < 2^15, biased towards 1
+ ...biasedRange(1, 2 ** 15, 1000),
+ ],
+ 'unfiltered',
+ FP.f16.inverseSqrtInterval
+ );
+ },
+ abstract: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ [
+ // 0 < x <= 1 linearly spread
+ ...linearRange(kValue.f64.positive.min, 1, 100),
+ // 1 <= x < 2^64, biased towards 1, only using 100 steps, because af tests are more expensive per case
+ ...biasedRange(1, 2 ** 64, 100),
+ ],
+ 'finite',
+ // inverseSqrt has an ulp accuracy, so is only expected to be as accurate as f32
+ FP.f32.inverseSqrtInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts
index 3e83816387..954718de6c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/inversesqrt.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'inverseSqrt' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn inverseSqrt(e: T ) -> T
Returns the reciprocal of sqrt(e). Component-wise when T is a vector.
@@ -9,51 +9,33 @@ Returns the reciprocal of sqrt(e). Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { kValue } from '../../../../../util/constants.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { biasedRange, linearRange } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './inversesqrt.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('inverseSqrt', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- // 0 < x <= 1 linearly spread
- ...linearRange(kValue.f32.positive.min, 1, 100),
- // 1 <= x < 2^32, biased towards 1
- ...biasedRange(1, 2 ** 32, 1000),
- ],
- 'unfiltered',
- FP.f32.inverseSqrtInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- [
- // 0 < x <= 1 linearly spread
- ...linearRange(kValue.f16.positive.min, 1, 100),
- // 1 <= x < 2^15, biased towards 1
- ...biasedRange(1, 2 ** 15, 1000),
- ],
- 'unfiltered',
- FP.f16.inverseSqrtInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('inverseSqrt'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -63,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('inverseSqrt'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('inverseSqrt'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -77,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('inverseSqrt'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('inverseSqrt'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.cache.ts
new file mode 100644
index 0000000000..cf9194319b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.cache.ts
@@ -0,0 +1,61 @@
+import { assert } from '../../../../../../common/util/util.js';
+import { anyOf } from '../../../../../util/compare.js';
+import { abstractInt, i32 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, quantizeToI32, sparseI32Range } from '../../../../../util/math.js';
+import { Case } from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// ldexpInterval's return interval doesn't cover the flush-to-zero cases when e2 + bias <= 0, thus
+// special examination is required.
+// See the comment block on ldexpInterval for more details
+// e2 is an integer (i32) while e1 is float.
+const makeCase = (trait: 'f32' | 'f16' | 'abstract', e1: number, e2: number): Case => {
+ const FPTrait = FP[trait];
+ e1 = FPTrait.quantize(e1);
+ // e2 should be in i32 range for the convenience.
+ assert(-2147483648 <= e2 && e2 <= 2147483647, 'e2 should be in i32 range');
+ e2 = quantizeToI32(e2);
+
+ const expected = FPTrait.ldexpInterval(e1, e2);
+
+ const e2_scalar = trait === 'abstract' ? abstractInt(BigInt(e2)) : i32(e2);
+ // Result may be zero if e2 + bias <= 0
+ if (e2 + FPTrait.constants().bias <= 0) {
+ return {
+ input: [FPTrait.scalarBuilder(e1), e2_scalar],
+ expected: anyOf(expected, FPTrait.constants().zeroInterval),
+ };
+ }
+
+ return { input: [FPTrait.scalarBuilder(e1), e2_scalar], expected };
+};
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (nonConst) {
+ if (trait === 'abstract') {
+ return [];
+ }
+ return FP[trait]
+ .sparseScalarRange()
+ .flatMap(e1 => sparseI32Range().map(e2 => makeCase(trait, e1, e2)));
+ }
+ const bias = FP[trait].constants().bias;
+ // const
+ return FP[trait]
+ .sparseScalarRange()
+ .flatMap(e1 =>
+ biasedRange(-bias - 10, bias + 1, 10).flatMap(e2 =>
+ FP[trait].isFinite(e1 * 2 ** quantizeToI32(e2)) ? makeCase(trait, e1, e2) : []
+ )
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('ldexp', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts
index 3829867752..d6cebfef6d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/ldexp.spec.ts
@@ -1,10 +1,10 @@
export const description = `
Execution tests for the 'ldexp' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
-K is AbstractInt, i32
+K is Type.abstractInt, i32
I is K or vecN<K>, where
I is a scalar if T is a scalar, or a vector when T is a vector
@@ -13,77 +13,15 @@ Returns e1 * 2^e2. Component-wise when T is a vector.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
-import { assert } from '../../../../../../common/util/util.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { anyOf } from '../../../../../util/compare.js';
-import { i32, TypeF32, TypeF16, TypeI32 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import {
- biasedRange,
- quantizeToI32,
- sparseF32Range,
- sparseI32Range,
- sparseF16Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, Case, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './ldexp.cache.js';
export const g = makeTestGroup(GPUTest);
-const bias = {
- f32: 127,
- f16: 15,
-} as const;
-
-// ldexpInterval's return interval doesn't cover the flush-to-zero cases when e2 + bias <= 0, thus
-// special examination is required.
-// See the comment block on ldexpInterval for more details
-// e2 is an integer (i32) while e1 is float.
-const makeCase = (trait: 'f32' | 'f16', e1: number, e2: number): Case => {
- const FPTrait = FP[trait];
- e1 = FPTrait.quantize(e1);
- // e2 should be in i32 range for the convinience.
- assert(-2147483648 <= e2 && e2 <= 2147483647, 'e2 should be in i32 range');
- e2 = quantizeToI32(e2);
-
- const expected = FPTrait.ldexpInterval(e1, e2);
-
- // Result may be zero if e2 + bias <= 0
- if (e2 + bias[trait] <= 0) {
- return {
- input: [FPTrait.scalarBuilder(e1), i32(e2)],
- expected: anyOf(expected, FPTrait.constants().zeroInterval),
- };
- }
-
- return { input: [FPTrait.scalarBuilder(e1), i32(e2)], expected };
-};
-
-export const d = makeCaseCache('ldexp', {
- f32_non_const: () => {
- return sparseF32Range().flatMap(e1 => sparseI32Range().map(e2 => makeCase('f32', e1, e2)));
- },
- f32_const: () => {
- return sparseF32Range().flatMap(e1 =>
- biasedRange(-bias.f32 - 10, bias.f32 + 1, 10).flatMap(e2 =>
- FP.f32.isFinite(e1 * 2 ** quantizeToI32(e2)) ? makeCase('f32', e1, e2) : []
- )
- );
- },
- f16_non_const: () => {
- return sparseF16Range().flatMap(e1 => sparseI32Range().map(e2 => makeCase('f16', e1, e2)));
- },
- f16_const: () => {
- return sparseF16Range().flatMap(e1 =>
- biasedRange(-bias.f16 - 10, bias.f16 + 1, 10).flatMap(e2 =>
- FP.f16.isFinite(e1 * 2 ** quantizeToI32(e2)) ? makeCase('f16', e1, e2) : []
- )
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(
@@ -91,9 +29,21 @@ g.test('abstract_float')
`
)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('ldexp'),
+ [Type.abstractFloat, Type.abstractInt],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -103,7 +53,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('ldexp'), [TypeF32, TypeI32], TypeF32, t.params, cases);
+ await run(t, builtin('ldexp'), [Type.f32, Type.i32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -117,5 +67,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('ldexp'), [TypeF16, TypeI32], TypeF16, t.params, cases);
+ await run(t, builtin('ldexp'), [Type.f16, Type.i32], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.cache.ts
new file mode 100644
index 0000000000..e7a2ee22ba
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.cache.ts
@@ -0,0 +1,42 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const scalar_cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ trait !== 'abstract' ? 'unfiltered' : 'finite',
+ // length has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].lengthInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: [f32|f16|abstract]_vecN_[non_]const
+const vec_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateVectorToIntervalCases(
+ FP[trait].vectorRange(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ // length has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].lengthInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('length', {
+ ...scalar_cases,
+ ...vec_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts
index 85c1f85169..735c8468b7 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/length.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'length' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn length(e: T ) -> f32
Returns the length of e (e.g. abs(e) if T is a scalar, or sqrt(e[0]^2 + e[1]^2 + ...) if T is a vector).
@@ -9,77 +9,77 @@ Returns the length of e (e.g. abs(e) if T is a scalar, or sqrt(e[0]^2 + e[1]^2 +
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import {
- fullF32Range,
- fullF16Range,
- vectorF32Range,
- vectorF16Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
-
-import { builtin } from './builtin.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './length.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: f32_vecN_[non_]const
-const f32_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorToIntervalCases(
- vectorF32Range(n),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.lengthInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_vecN_[non_]const
-const f16_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorToIntervalCases(
- vectorF16Range(n),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.lengthInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('length', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- fullF32Range(),
- 'unfiltered',
- FP.f32.lengthInterval
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract_float tests`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('length'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_float_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec2s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_const');
+ await run(
+ t,
+ abstractFloatBuiltin('length'),
+ [Type.vec2af],
+ Type.abstractFloat,
+ t.params,
+ cases
);
- },
- ...f32_vec_cases,
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- fullF16Range(),
- 'unfiltered',
- FP.f16.lengthInterval
+ });
+
+g.test('abstract_float_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract_float tests using vec3s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_const');
+ await run(
+ t,
+ abstractFloatBuiltin('length'),
+ [Type.vec3af],
+ Type.abstractFloat,
+ t.params,
+ cases
);
- },
- ...f16_vec_cases,
-});
+ });
-g.test('abstract_float')
+g.test('abstract_float_vec4')
.specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
- .desc(`abstract float tests`)
- .params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
- )
- .unimplemented();
+ .desc(`abstract_float tests using vec4s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_const');
+ await run(
+ t,
+ abstractFloatBuiltin('length'),
+ [Type.vec4af],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
@@ -87,7 +87,7 @@ g.test('f32')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('length'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('length'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f32_vec2')
@@ -98,7 +98,7 @@ g.test('f32_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
);
- await run(t, builtin('length'), [TypeVec(2, TypeF32)], TypeF32, t.params, cases);
+ await run(t, builtin('length'), [Type.vec2f], Type.f32, t.params, cases);
});
g.test('f32_vec3')
@@ -109,7 +109,7 @@ g.test('f32_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
);
- await run(t, builtin('length'), [TypeVec(3, TypeF32)], TypeF32, t.params, cases);
+ await run(t, builtin('length'), [Type.vec3f], Type.f32, t.params, cases);
});
g.test('f32_vec4')
@@ -120,7 +120,7 @@ g.test('f32_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
);
- await run(t, builtin('length'), [TypeVec(4, TypeF32)], TypeF32, t.params, cases);
+ await run(t, builtin('length'), [Type.vec4f], Type.f32, t.params, cases);
});
g.test('f16')
@@ -132,7 +132,7 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('length'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('length'), [Type.f16], Type.f16, t.params, cases);
});
g.test('f16_vec2')
@@ -146,7 +146,7 @@ g.test('f16_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
);
- await run(t, builtin('length'), [TypeVec(2, TypeF16)], TypeF16, t.params, cases);
+ await run(t, builtin('length'), [Type.vec2h], Type.f16, t.params, cases);
});
g.test('f16_vec3')
@@ -160,7 +160,7 @@ g.test('f16_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
);
- await run(t, builtin('length'), [TypeVec(3, TypeF16)], TypeF16, t.params, cases);
+ await run(t, builtin('length'), [Type.vec3h], Type.f16, t.params, cases);
});
g.test('f16_vec4')
@@ -174,5 +174,5 @@ g.test('f16_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
);
- await run(t, builtin('length'), [TypeVec(4, TypeF16)], TypeF16, t.params, cases);
+ await run(t, builtin('length'), [Type.vec4h], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.cache.ts
new file mode 100644
index 0000000000..76b8bfa1af
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.cache.ts
@@ -0,0 +1,30 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// log's accuracy is defined in three regions { [0, 0.5), [0.5, 2.0], (2.0, +∞] }
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ [
+ ...linearRange(FP[trait].constants().positive.min, 0.5, 20),
+ ...linearRange(0.5, 2.0, 20),
+ ...biasedRange(2.0, 2 ** 32, 1000),
+ ...FP[trait].scalarRange(),
+ ],
+ nonConst ? 'unfiltered' : 'finite',
+ // log has an absolute or ulp accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].logInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('log', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts
index ac60e2b1bc..99b4a6983e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'log' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn log(e: T ) -> T
Returns the natural logarithm of e. Component-wise when T is a vector.
@@ -9,53 +9,33 @@ Returns the natural logarithm of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { kValue } from '../../../../../util/constants.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { biasedRange, fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './log.cache.js';
export const g = makeTestGroup(GPUTest);
-// log's accuracy is defined in three regions { [0, 0.5), [0.5, 2.0], (2.0, +∞] }
-const f32_inputs = [
- ...linearRange(kValue.f32.positive.min, 0.5, 20),
- ...linearRange(0.5, 2.0, 20),
- ...biasedRange(2.0, 2 ** 32, 1000),
- ...fullF32Range(),
-];
-const f16_inputs = [
- ...linearRange(kValue.f16.positive.min, 0.5, 20),
- ...linearRange(0.5, 2.0, 20),
- ...biasedRange(2.0, 2 ** 32, 1000),
- ...fullF16Range(),
-];
-
-export const d = makeCaseCache('log', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.logInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.logInterval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.logInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.logInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('log'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -71,7 +51,7 @@ TODO(#792): Decide what the ground-truth is for these tests. [1]
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('log'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('log'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -85,5 +65,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('log'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('log'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.cache.ts
new file mode 100644
index 0000000000..d7781f328d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.cache.ts
@@ -0,0 +1,30 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { biasedRange, linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// log2's accuracy is defined in three regions { [0, 0.5), [0.5, 2.0], (2.0, +∞] }
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ [
+ ...linearRange(FP[trait].constants().positive.min, 0.5, 20),
+ ...linearRange(0.5, 2.0, 20),
+ ...biasedRange(2.0, 2 ** 32, 1000),
+ ...FP[trait].scalarRange(),
+ ],
+ nonConst ? 'unfiltered' : 'finite',
+ // log2 has an absolute or ulp accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].log2Interval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('log2', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts
index 37931579b9..dc623a6784 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/log2.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'log2' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn log2(e: T ) -> T
Returns the base-2 logarithm of e. Component-wise when T is a vector.
@@ -9,53 +9,33 @@ Returns the base-2 logarithm of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { kValue } from '../../../../../util/constants.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { biasedRange, fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './log2.cache.js';
export const g = makeTestGroup(GPUTest);
-// log2's accuracy is defined in three regions { [0, 0.5), [0.5, 2.0], (2.0, +∞] }
-const f32_inputs = [
- ...linearRange(kValue.f32.positive.min, 0.5, 20),
- ...linearRange(0.5, 2.0, 20),
- ...biasedRange(2.0, 2 ** 32, 1000),
- ...fullF32Range(),
-];
-const f16_inputs = [
- ...linearRange(kValue.f16.positive.min, 0.5, 20),
- ...linearRange(0.5, 2.0, 20),
- ...biasedRange(2.0, 2 ** 32, 1000),
- ...fullF16Range(),
-];
-
-export const d = makeCaseCache('log2', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'finite', FP.f32.log2Interval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(f32_inputs, 'unfiltered', FP.f32.log2Interval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'finite', FP.f16.log2Interval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(f16_inputs, 'unfiltered', FP.f16.log2Interval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('log2'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -71,7 +51,7 @@ TODO(#792): Decide what the ground-truth is for these tests. [1]
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('log2'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('log2'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -85,5 +65,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('log2'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('log2'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.cache.ts
new file mode 100644
index 0000000000..72def03dab
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.cache.ts
@@ -0,0 +1,18 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarPairToIntervalCases(
+ FP[trait].sparseScalarRange(),
+ FP[trait].sparseScalarRange(),
+ 'unfiltered',
+ FP[trait].maxInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('max', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts
index 6654b4951c..ee7cb0d674 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/max.spec.ts
@@ -1,12 +1,12 @@
export const description = `
Execution tests for the 'max' builtin function
-S is AbstractInt, i32, or u32
+S is abstract-int, i32, or u32
T is S or vecN<S>
@const fn max(e1: T ,e2: T) -> T
Returns e2 if e1 is less than e2, and e1 otherwise. Component-wise when T is a vector.
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is vecN<S>
@const fn max(e1: T ,e2: T) -> T
Returns e2 if e1 is less than e2, and e1 otherwise.
@@ -18,72 +18,50 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import {
- i32,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeU32,
- u32,
- TypeAbstractFloat,
-} from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range, sparseF64Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, Case, onlyConstInputSource, run } from '../../expression.js';
-
-import { abstractBuiltin, builtin } from './builtin.js';
+import { Type, i32, u32, abstractInt } from '../../../../../util/conversion.js';
+import { maxBigInt } from '../../../../../util/math.js';
+import { Case } from '../../case.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
+
+import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js';
+import { d } from './max.cache.js';
/** Generate set of max test cases from list of interesting values */
-function generateTestCases(
- values: Array<number>,
- makeCase: (x: number, y: number) => Case
-): Array<Case> {
- const cases = new Array<Case>();
- values.forEach(e => {
- values.forEach(f => {
- cases.push(makeCase(e, f));
+function generateTestCases<Type>(values: Type[], makeCase: (x: Type, y: Type) => Case): Case[] {
+ return values.flatMap(e => {
+ return values.map(f => {
+ return makeCase(e, f);
});
});
- return cases;
}
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('max', {
- f32: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- fullF32Range(),
- fullF32Range(),
- 'unfiltered',
- FP.f32.maxInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- fullF16Range(),
- fullF16Range(),
- 'unfiltered',
- FP.f16.maxInterval
- );
- },
- abstract: () => {
- return FP.abstract.generateScalarPairToIntervalCases(
- sparseF64Range(),
- sparseF64Range(),
- 'unfiltered',
- FP.abstract.maxInterval
- );
- },
-});
-
g.test('abstract_int')
.specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
.desc(`abstract int tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const makeCase = (x: bigint, y: bigint): Case => {
+ return { input: [abstractInt(x), abstractInt(y)], expected: abstractInt(maxBigInt(x, y)) };
+ };
+
+ const test_values = [-0x70000000n, -2n, -1n, 0n, 1n, 2n, 0x70000000n];
+ const cases = generateTestCases(test_values, makeCase);
+
+ await run(
+ t,
+ abstractIntBuiltin('max'),
+ [Type.abstractInt, Type.abstractInt],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
g.test('u32')
.specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
@@ -96,10 +74,10 @@ g.test('u32')
return { input: [u32(x), u32(y)], expected: u32(Math.max(x, y)) };
};
- const test_values: Array<number> = [0, 1, 2, 0x70000000, 0x80000000, 0xffffffff];
+ const test_values: number[] = [0, 1, 2, 0x70000000, 0x80000000, 0xffffffff];
const cases = generateTestCases(test_values, makeCase);
- await run(t, builtin('max'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, builtin('max'), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('i32')
@@ -113,10 +91,10 @@ g.test('i32')
return { input: [i32(x), i32(y)], expected: i32(Math.max(x, y)) };
};
- const test_values: Array<number> = [-0x70000000, -2, -1, 0, 1, 2, 0x70000000];
+ const test_values: number[] = [-0x70000000, -2, -1, 0, 1, 2, 0x70000000];
const cases = generateTestCases(test_values, makeCase);
- await run(t, builtin('max'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, builtin('max'), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('abstract_float')
@@ -131,9 +109,9 @@ g.test('abstract_float')
const cases = await d.get('abstract');
await run(
t,
- abstractBuiltin('max'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBuiltin('max'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -147,7 +125,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('max'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('max'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -161,5 +139,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('max'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('max'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.cache.ts
new file mode 100644
index 0000000000..cddca325f0
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.cache.ts
@@ -0,0 +1,18 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarPairToIntervalCases(
+ FP[trait].sparseScalarRange(),
+ FP[trait].sparseScalarRange(),
+ 'unfiltered',
+ FP[trait].minInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('min', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts
index 6c05319546..ac63641399 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/min.spec.ts
@@ -1,12 +1,12 @@
export const description = `
Execution tests for the 'min' builtin function
-S is AbstractInt, i32, or u32
+S is abstract-int, i32, or u32
T is S or vecN<S>
@const fn min(e1: T ,e2: T) -> T
Returns e1 if e1 is less than e2, and e2 otherwise. Component-wise when T is a vector.
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn min(e1: T ,e2: T) -> T
Returns e2 if e2 is less than e1, and e1 otherwise.
@@ -17,72 +17,50 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import {
- i32,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeU32,
- u32,
- TypeAbstractFloat,
-} from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range, sparseF64Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, Case, onlyConstInputSource, run } from '../../expression.js';
-
-import { abstractBuiltin, builtin } from './builtin.js';
+import { Type, i32, u32, abstractInt } from '../../../../../util/conversion.js';
+import { minBigInt } from '../../../../../util/math.js';
+import { Case } from '../../case.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-export const g = makeTestGroup(GPUTest);
+import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js';
+import { d } from './min.cache.js';
-export const d = makeCaseCache('min', {
- f32: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- fullF32Range(),
- fullF32Range(),
- 'unfiltered',
- FP.f32.minInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- fullF16Range(),
- fullF16Range(),
- 'unfiltered',
- FP.f16.minInterval
- );
- },
- abstract: () => {
- return FP.abstract.generateScalarPairToIntervalCases(
- sparseF64Range(),
- sparseF64Range(),
- 'unfiltered',
- FP.abstract.minInterval
- );
- },
-});
+export const g = makeTestGroup(GPUTest);
/** Generate set of min test cases from list of interesting values */
-function generateTestCases(
- values: Array<number>,
- makeCase: (x: number, y: number) => Case
-): Array<Case> {
- const cases = new Array<Case>();
- values.forEach(e => {
- values.forEach(f => {
- cases.push(makeCase(e, f));
+function generateTestCases<Type>(values: Type[], makeCase: (x: Type, y: Type) => Case): Case[] {
+ return values.flatMap(e => {
+ return values.map(f => {
+ return makeCase(e, f);
});
});
- return cases;
}
g.test('abstract_int')
.specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
.desc(`abstract int tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const makeCase = (x: bigint, y: bigint): Case => {
+ return { input: [abstractInt(x), abstractInt(y)], expected: abstractInt(minBigInt(x, y)) };
+ };
+
+ const test_values = [-0x70000000n, -2n, -1n, 0n, 1n, 2n, 0x70000000n];
+ const cases = generateTestCases(test_values, makeCase);
+
+ await run(
+ t,
+ abstractIntBuiltin('min'),
+ [Type.abstractInt, Type.abstractInt],
+ Type.abstractInt,
+ t.params,
+ cases
+ );
+ });
g.test('u32')
.specURL('https://www.w3.org/TR/WGSL/#integer-builtin-functions')
@@ -95,10 +73,10 @@ g.test('u32')
return { input: [u32(x), u32(y)], expected: u32(Math.min(x, y)) };
};
- const test_values: Array<number> = [0, 1, 2, 0x70000000, 0x80000000, 0xffffffff];
+ const test_values: number[] = [0, 1, 2, 0x70000000, 0x80000000, 0xffffffff];
const cases = generateTestCases(test_values, makeCase);
- await run(t, builtin('min'), [TypeU32, TypeU32], TypeU32, t.params, cases);
+ await run(t, builtin('min'), [Type.u32, Type.u32], Type.u32, t.params, cases);
});
g.test('i32')
@@ -112,10 +90,10 @@ g.test('i32')
return { input: [i32(x), i32(y)], expected: i32(Math.min(x, y)) };
};
- const test_values: Array<number> = [-0x70000000, -2, -1, 0, 1, 2, 0x70000000];
+ const test_values: number[] = [-0x70000000, -2, -1, 0, 1, 2, 0x70000000];
const cases = generateTestCases(test_values, makeCase);
- await run(t, builtin('min'), [TypeI32, TypeI32], TypeI32, t.params, cases);
+ await run(t, builtin('min'), [Type.i32, Type.i32], Type.i32, t.params, cases);
});
g.test('abstract_float')
@@ -130,9 +108,9 @@ g.test('abstract_float')
const cases = await d.get('abstract');
await run(
t,
- abstractBuiltin('min'),
- [TypeAbstractFloat, TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBuiltin('min'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -146,7 +124,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('min'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('min'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -160,5 +138,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('min'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('min'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.cache.ts
new file mode 100644
index 0000000000..be221297b0
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.cache.ts
@@ -0,0 +1,56 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { selectNCases } from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+// abstract_non_const is empty and unused
+const scalar_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ const cases = FP[trait].generateScalarTripleToIntervalCases(
+ FP[trait].sparseScalarRange(),
+ FP[trait].sparseScalarRange(),
+ FP[trait].sparseScalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // mix has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ ...FP[trait !== 'abstract' ? trait : 'f32'].mixIntervals
+ );
+ return selectNCases('mix_scalar', trait === 'abstract' ? 50 : cases.length, cases);
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: [f32|f16]_vecN_scalar_[non_]const
+// abstract_vecN_non_const is empty and unused
+const vec_scalar_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_vec${dim}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ const cases = FP[trait].generateVectorPairScalarToVectorComponentWiseCase(
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseScalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // mix has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ ...FP[trait !== 'abstract' ? trait : 'f32'].mixIntervals
+ );
+ return selectNCases('mix_vector', trait === 'abstract' ? 50 : cases.length, cases);
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('mix', {
+ ...scalar_cases,
+ ...vec_scalar_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts
index 95e9f6b310..0005ab5c0e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/mix.spec.ts
@@ -1,12 +1,12 @@
export const description = `
Execution tests for the 'mix' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn mix(e1: T, e2: T, e3: T) -> T
Returns the linear blend of e1 and e2 (e.g. e1*(1-e3)+e2*e3). Component-wise when T is a vector.
-T is AbstractFloat, f32, or f16
+T is abstract-float, f32, or f16
T2 is vecN<T>
@const fn mix(e1: T2, e2: T2, e3: T) -> T2
Returns the component-wise linear blend of e1 and e2, using scalar blending factor e3 for each component.
@@ -16,121 +16,81 @@ Same as mix(e1,e2,T2(e3)).
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeVec, TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import {
- sparseF32Range,
- sparseF16Range,
- sparseVectorF32Range,
- sparseVectorF16Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './mix.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: f32_vecN_scalar_[non_]const
-const f32_vec_scalar_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f32_vec${n}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorPairScalarToVectorComponentWiseCase(
- sparseVectorF32Range(n),
- sparseVectorF32Range(n),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite',
- ...FP.f32.mixIntervals
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_vecN_scalar_[non_]const
-const f16_vec_scalar_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f16_vec${n}_scalar_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorPairScalarToVectorComponentWiseCase(
- sparseVectorF16Range(n),
- sparseVectorF16Range(n),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite',
- ...FP.f16.mixIntervals
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('mix', {
- f32_const: () => {
- return FP.f32.generateScalarTripleToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- sparseF32Range(),
- 'finite',
- ...FP.f32.mixIntervals
- );
- },
- f32_non_const: () => {
- return FP.f32.generateScalarTripleToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- sparseF32Range(),
- 'unfiltered',
- ...FP.f32.mixIntervals
- );
- },
- ...f32_vec_scalar_cases,
- f16_const: () => {
- return FP.f16.generateScalarTripleToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- sparseF16Range(),
- 'finite',
- ...FP.f16.mixIntervals
- );
- },
- f16_non_const: () => {
- return FP.f16.generateScalarTripleToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- sparseF16Range(),
- 'unfiltered',
- ...FP.f16.mixIntervals
- );
- },
- ...f16_vec_scalar_cases,
-});
-
g.test('abstract_float_matching')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract_float test with matching third param`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('mix'),
+ [Type.abstractFloat, Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('abstract_float_nonmatching_vec2')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract_float tests with two vec2<abstract_float> params and scalar third param`)
- .params(u => u.combine('inputSource', allInputSources))
- .unimplemented();
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_scalar_const');
+ await run(
+ t,
+ abstractFloatBuiltin('mix'),
+ [Type.vec(2, Type.abstractFloat), Type.vec(2, Type.abstractFloat), Type.abstractFloat],
+ Type.vec(2, Type.abstractFloat),
+ t.params,
+ cases
+ );
+ });
g.test('abstract_float_nonmatching_vec3')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract_float tests with two vec3<abstract_float> params and scalar third param`)
- .params(u => u.combine('inputSource', allInputSources))
- .unimplemented();
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_scalar_const');
+ await run(
+ t,
+ abstractFloatBuiltin('mix'),
+ [Type.vec(3, Type.abstractFloat), Type.vec(3, Type.abstractFloat), Type.abstractFloat],
+ Type.vec(3, Type.abstractFloat),
+ t.params,
+ cases
+ );
+ });
g.test('abstract_float_nonmatching_vec4')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract_float tests with two vec4<abstract_float> params and scalar third param`)
- .params(u => u.combine('inputSource', allInputSources))
- .unimplemented();
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_scalar_const');
+ await run(
+ t,
+ abstractFloatBuiltin('mix'),
+ [Type.vec(4, Type.abstractFloat), Type.vec(4, Type.abstractFloat), Type.abstractFloat],
+ Type.vec(4, Type.abstractFloat),
+ t.params,
+ cases
+ );
+ });
g.test('f32_matching')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -140,7 +100,7 @@ g.test('f32_matching')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('mix'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('mix'), [Type.f32, Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f32_nonmatching_vec2')
@@ -151,14 +111,7 @@ g.test('f32_nonmatching_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec2_scalar_const' : 'f32_vec2_scalar_non_const'
);
- await run(
- t,
- builtin('mix'),
- [TypeVec(2, TypeF32), TypeVec(2, TypeF32), TypeF32],
- TypeVec(2, TypeF32),
- t.params,
- cases
- );
+ await run(t, builtin('mix'), [Type.vec2f, Type.vec2f, Type.f32], Type.vec2f, t.params, cases);
});
g.test('f32_nonmatching_vec3')
@@ -169,14 +122,7 @@ g.test('f32_nonmatching_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec3_scalar_const' : 'f32_vec3_scalar_non_const'
);
- await run(
- t,
- builtin('mix'),
- [TypeVec(3, TypeF32), TypeVec(3, TypeF32), TypeF32],
- TypeVec(3, TypeF32),
- t.params,
- cases
- );
+ await run(t, builtin('mix'), [Type.vec3f, Type.vec3f, Type.f32], Type.vec3f, t.params, cases);
});
g.test('f32_nonmatching_vec4')
@@ -187,14 +133,7 @@ g.test('f32_nonmatching_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec4_scalar_const' : 'f32_vec4_scalar_non_const'
);
- await run(
- t,
- builtin('mix'),
- [TypeVec(4, TypeF32), TypeVec(4, TypeF32), TypeF32],
- TypeVec(4, TypeF32),
- t.params,
- cases
- );
+ await run(t, builtin('mix'), [Type.vec4f, Type.vec4f, Type.f32], Type.vec4f, t.params, cases);
});
g.test('f16_matching')
@@ -208,7 +147,7 @@ g.test('f16_matching')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('mix'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('mix'), [Type.f16, Type.f16, Type.f16], Type.f16, t.params, cases);
});
g.test('f16_nonmatching_vec2')
@@ -222,14 +161,7 @@ g.test('f16_nonmatching_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec2_scalar_const' : 'f16_vec2_scalar_non_const'
);
- await run(
- t,
- builtin('mix'),
- [TypeVec(2, TypeF16), TypeVec(2, TypeF16), TypeF16],
- TypeVec(2, TypeF16),
- t.params,
- cases
- );
+ await run(t, builtin('mix'), [Type.vec2h, Type.vec2h, Type.f16], Type.vec2h, t.params, cases);
});
g.test('f16_nonmatching_vec3')
@@ -243,14 +175,7 @@ g.test('f16_nonmatching_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec3_scalar_const' : 'f16_vec3_scalar_non_const'
);
- await run(
- t,
- builtin('mix'),
- [TypeVec(3, TypeF16), TypeVec(3, TypeF16), TypeF16],
- TypeVec(3, TypeF16),
- t.params,
- cases
- );
+ await run(t, builtin('mix'), [Type.vec3h, Type.vec3h, Type.f16], Type.vec3h, t.params, cases);
});
g.test('f16_nonmatching_vec4')
@@ -264,12 +189,5 @@ g.test('f16_nonmatching_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec4_scalar_const' : 'f16_vec4_scalar_non_const'
);
- await run(
- t,
- builtin('mix'),
- [TypeVec(4, TypeF16), TypeVec(4, TypeF16), TypeF16],
- TypeVec(4, TypeF16),
- t.params,
- cases
- );
+ await run(t, builtin('mix'), [Type.vec4h, Type.vec4h, Type.f16], Type.vec4h, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.cache.ts
new file mode 100644
index 0000000000..1a76de56bb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.cache.ts
@@ -0,0 +1,75 @@
+import { toVector } from '../../../../../util/conversion.js';
+import { FP, FPKind } from '../../../../../util/floating_point.js';
+import { Case } from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+/** @returns a fract Case for a scalar vector input */
+function makeScalarCaseFract(kind: FPKind, n: number): Case {
+ const fp = FP[kind];
+ n = fp.quantize(n);
+ const result = fp.modfInterval(n).fract;
+
+ return { input: fp.scalarBuilder(n), expected: result };
+}
+
+/** @returns a whole Case for a scalar vector input */
+function makeScalarCaseWhole(kind: FPKind, n: number): Case {
+ const fp = FP[kind];
+ n = fp.quantize(n);
+ const result = fp.modfInterval(n).whole;
+
+ return { input: fp.scalarBuilder(n), expected: result };
+}
+
+/** @returns a fract Case for a given vector input */
+function makeVectorCaseFract(kind: FPKind, v: readonly number[]): Case {
+ const fp = FP[kind];
+ v = v.map(fp.quantize);
+ const fs = v.map(e => {
+ return fp.modfInterval(e).fract;
+ });
+
+ return { input: toVector(v, fp.scalarBuilder), expected: fs };
+}
+
+/** @returns a whole Case for a given vector input */
+function makeVectorCaseWhole(kind: FPKind, v: readonly number[]): Case {
+ const fp = FP[kind];
+ v = v.map(fp.quantize);
+ const ws = v.map(e => {
+ return fp.modfInterval(e).whole;
+ });
+
+ return { input: toVector(v, fp.scalarBuilder), expected: ws };
+}
+
+// Cases: [f32|f16|abstract]_[fract|whole]
+const scalar_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(kind =>
+ (['whole', 'fract'] as const).map(portion => ({
+ [`${kind}_${portion}`]: () => {
+ const makeCase = portion === 'whole' ? makeScalarCaseWhole : makeScalarCaseFract;
+ return FP[kind].scalarRange().map(makeCase.bind(null, kind));
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: [f32|f16|abstract]_vecN_[fract|whole]
+const vec_cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(kind =>
+ ([2, 3, 4] as const).flatMap(n =>
+ (['whole', 'fract'] as const).map(portion => ({
+ [`${kind}_vec${n}_${portion}`]: () => {
+ const makeCase = portion === 'whole' ? makeVectorCaseWhole : makeVectorCaseFract;
+ return FP[kind].vectorRange(n).map(makeCase.bind(null, kind));
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('modf', {
+ ...scalar_cases,
+ ...vec_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts
index 1a3d8a2850..6c988008f8 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/modf.spec.ts
@@ -1,13 +1,13 @@
export const description = `
Execution tests for the 'modf' builtin function
-T is f32 or f16 or AbstractFloat
+T is f32 or f16 or Type.abstractFloat
@const fn modf(e:T) -> result_struct
Splits |e| into fractional and whole number parts.
The whole part is (|e| % 1.0), and the fractional part is |e| minus the whole part.
Returns the result_struct for the given type.
-S is f32 or f16 or AbstractFloat
+S is f32 or f16 or Type.abstractFloat
T is vecN<S>
@const fn modf(e:T) -> result_struct
Splits the components of |e| into fractional and whole number parts.
@@ -18,33 +18,18 @@ Returns the result_struct for the given type.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import {
- toVector,
- TypeAbstractFloat,
- TypeF16,
- TypeF32,
- TypeVec,
-} from '../../../../../util/conversion.js';
-import { FP, FPKind } from '../../../../../util/floating_point.js';
-import {
- fullF16Range,
- fullF32Range,
- fullF64Range,
- vectorF16Range,
- vectorF32Range,
- vectorF64Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import {
abstractFloatShaderBuilder,
allInputSources,
basicExpressionBuilder,
- Case,
onlyConstInputSource,
run,
ShaderBuilder,
} from '../../expression.js';
+import { d } from './modf.cache.js';
+
export const g = makeTestGroup(GPUTest);
/** @returns an ShaderBuilder that evaluates modf and returns .whole from the result structure */
@@ -67,101 +52,6 @@ function abstractFractBuilder(): ShaderBuilder {
return abstractFloatShaderBuilder(value => `modf(${value}).fract`);
}
-/** @returns a fract Case for a scalar vector input */
-function makeScalarCaseFract(kind: FPKind, n: number): Case {
- const fp = FP[kind];
- n = fp.quantize(n);
- const result = fp.modfInterval(n).fract;
-
- return { input: fp.scalarBuilder(n), expected: result };
-}
-
-/** @returns a whole Case for a scalar vector input */
-function makeScalarCaseWhole(kind: FPKind, n: number): Case {
- const fp = FP[kind];
- n = fp.quantize(n);
- const result = fp.modfInterval(n).whole;
-
- return { input: fp.scalarBuilder(n), expected: result };
-}
-
-/** @returns a fract Case for a given vector input */
-function makeVectorCaseFract(kind: FPKind, v: readonly number[]): Case {
- const fp = FP[kind];
- v = v.map(fp.quantize);
- const fs = v.map(e => {
- return fp.modfInterval(e).fract;
- });
-
- return { input: toVector(v, fp.scalarBuilder), expected: fs };
-}
-
-/** @returns a whole Case for a given vector input */
-function makeVectorCaseWhole(kind: FPKind, v: readonly number[]): Case {
- const fp = FP[kind];
- v = v.map(fp.quantize);
- const ws = v.map(e => {
- return fp.modfInterval(e).whole;
- });
-
- return { input: toVector(v, fp.scalarBuilder), expected: ws };
-}
-
-const scalar_range = {
- f32: fullF32Range(),
- f16: fullF16Range(),
- abstract: fullF64Range(),
-};
-
-const vector_range = {
- f32: {
- 2: vectorF32Range(2),
- 3: vectorF32Range(3),
- 4: vectorF32Range(4),
- },
- f16: {
- 2: vectorF16Range(2),
- 3: vectorF16Range(3),
- 4: vectorF16Range(4),
- },
- abstract: {
- 2: vectorF64Range(2),
- 3: vectorF64Range(3),
- 4: vectorF64Range(4),
- },
-};
-
-// Cases: [f32|f16|abstract]_[fract|whole]
-const scalar_cases = (['f32', 'f16', 'abstract'] as const)
- .flatMap(kind =>
- (['whole', 'fract'] as const).map(portion => ({
- [`${kind}_${portion}`]: () => {
- const makeCase = portion === 'whole' ? makeScalarCaseWhole : makeScalarCaseFract;
- return scalar_range[kind].map(makeCase.bind(null, kind));
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: [f32|f16|abstract]_vecN_[fract|whole]
-const vec_cases = (['f32', 'f16', 'abstract'] as const)
- .flatMap(kind =>
- ([2, 3, 4] as const).flatMap(n =>
- (['whole', 'fract'] as const).map(portion => ({
- [`${kind}_vec${n}_${portion}`]: () => {
- const makeCase = portion === 'whole' ? makeVectorCaseWhole : makeVectorCaseFract;
- return vector_range[kind][n].map(makeCase.bind(null, kind));
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('modf', {
- ...scalar_cases,
- ...vec_cases,
-});
-
g.test('f32_fract')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(
@@ -177,7 +67,7 @@ struct __modf_result_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_fract');
- await run(t, fractBuilder(), [TypeF32], TypeF32, t.params, cases);
+ await run(t, fractBuilder(), [Type.f32], Type.f32, t.params, cases);
});
g.test('f32_whole')
@@ -195,7 +85,7 @@ struct __modf_result_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_whole');
- await run(t, wholeBuilder(), [TypeF32], TypeF32, t.params, cases);
+ await run(t, wholeBuilder(), [Type.f32], Type.f32, t.params, cases);
});
g.test('f32_vec2_fract')
@@ -213,7 +103,7 @@ struct __modf_result_vec2_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec2_fract');
- await run(t, fractBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec2f], Type.vec2f, t.params, cases);
});
g.test('f32_vec2_whole')
@@ -231,7 +121,7 @@ struct __modf_result_vec2_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec2_whole');
- await run(t, wholeBuilder(), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases);
+ await run(t, wholeBuilder(), [Type.vec2f], Type.vec2f, t.params, cases);
});
g.test('f32_vec3_fract')
@@ -249,7 +139,7 @@ struct __modf_result_vec3_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec3_fract');
- await run(t, fractBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec3f], Type.vec3f, t.params, cases);
});
g.test('f32_vec3_whole')
@@ -267,7 +157,7 @@ struct __modf_result_vec3_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec3_whole');
- await run(t, wholeBuilder(), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases);
+ await run(t, wholeBuilder(), [Type.vec3f], Type.vec3f, t.params, cases);
});
g.test('f32_vec4_fract')
@@ -285,7 +175,7 @@ struct __modf_result_vec4_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec4_fract');
- await run(t, fractBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec4f], Type.vec4f, t.params, cases);
});
g.test('f32_vec4_whole')
@@ -303,7 +193,7 @@ struct __modf_result_vec4_f32 {
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get('f32_vec4_whole');
- await run(t, wholeBuilder(), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases);
+ await run(t, wholeBuilder(), [Type.vec4f], Type.vec4f, t.params, cases);
});
g.test('f16_fract')
@@ -324,7 +214,7 @@ struct __modf_result_f16 {
})
.fn(async t => {
const cases = await d.get('f16_fract');
- await run(t, fractBuilder(), [TypeF16], TypeF16, t.params, cases);
+ await run(t, fractBuilder(), [Type.f16], Type.f16, t.params, cases);
});
g.test('f16_whole')
@@ -345,7 +235,7 @@ struct __modf_result_f16 {
})
.fn(async t => {
const cases = await d.get('f16_whole');
- await run(t, wholeBuilder(), [TypeF16], TypeF16, t.params, cases);
+ await run(t, wholeBuilder(), [Type.f16], Type.f16, t.params, cases);
});
g.test('f16_vec2_fract')
@@ -366,7 +256,7 @@ struct __modf_result_vec2_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec2_fract');
- await run(t, fractBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec2h], Type.vec2h, t.params, cases);
});
g.test('f16_vec2_whole')
@@ -387,7 +277,7 @@ struct __modf_result_vec2_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec2_whole');
- await run(t, wholeBuilder(), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases);
+ await run(t, wholeBuilder(), [Type.vec2h], Type.vec2h, t.params, cases);
});
g.test('f16_vec3_fract')
@@ -408,7 +298,7 @@ struct __modf_result_vec3_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec3_fract');
- await run(t, fractBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec3h], Type.vec3h, t.params, cases);
});
g.test('f16_vec3_whole')
@@ -429,7 +319,7 @@ struct __modf_result_vec3_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec3_whole');
- await run(t, wholeBuilder(), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases);
+ await run(t, wholeBuilder(), [Type.vec3h], Type.vec3h, t.params, cases);
});
g.test('f16_vec4_fract')
@@ -450,7 +340,7 @@ struct __modf_result_vec4_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec4_fract');
- await run(t, fractBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases);
+ await run(t, fractBuilder(), [Type.vec4h], Type.vec4h, t.params, cases);
});
g.test('f16_vec4_whole')
@@ -471,43 +361,43 @@ struct __modf_result_vec4_f16 {
})
.fn(async t => {
const cases = await d.get('f16_vec4_whole');
- await run(t, wholeBuilder(), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases);
+ await run(t, wholeBuilder(), [Type.vec4h], Type.vec4h, t.params, cases);
});
g.test('abstract_fract')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(
`
-T is AbstractFloat
+T is abstract-float
struct __modf_result_abstract {
- fract : AbstractFloat, // fractional part
- whole : AbstractFloat // whole part
+ fract : Type.abstractFloat, // fractional part
+ whole : Type.abstractFloat // whole part
}
`
)
.params(u => u.combine('inputSource', onlyConstInputSource))
.fn(async t => {
const cases = await d.get('abstract_fract');
- await run(t, abstractFractBuilder(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+ await run(t, abstractFractBuilder(), [Type.abstractFloat], Type.abstractFloat, t.params, cases);
});
g.test('abstract_whole')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(
`
-T is AbstractFloat
+T is abstract-float
struct __modf_result_abstract {
- fract : AbstractFloat, // fractional part
- whole : AbstractFloat // whole part
+ fract : Type.abstractFloat, // fractional part
+ whole : Type.abstractFloat // whole part
}
`
)
.params(u => u.combine('inputSource', onlyConstInputSource))
.fn(async t => {
const cases = await d.get('abstract_whole');
- await run(t, abstractWholeBuilder(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+ await run(t, abstractWholeBuilder(), [Type.abstractFloat], Type.abstractFloat, t.params, cases);
});
g.test('abstract_vec2_fract')
@@ -528,8 +418,8 @@ struct __modf_result_vec2_abstract {
await run(
t,
abstractFractBuilder(),
- [TypeVec(2, TypeAbstractFloat)],
- TypeVec(2, TypeAbstractFloat),
+ [Type.vec(2, Type.abstractFloat)],
+ Type.vec(2, Type.abstractFloat),
t.params,
cases
);
@@ -553,8 +443,8 @@ struct __modf_result_vec2_abstract {
await run(
t,
abstractWholeBuilder(),
- [TypeVec(2, TypeAbstractFloat)],
- TypeVec(2, TypeAbstractFloat),
+ [Type.vec(2, Type.abstractFloat)],
+ Type.vec(2, Type.abstractFloat),
t.params,
cases
);
@@ -578,8 +468,8 @@ struct __modf_result_vec3_abstract {
await run(
t,
abstractFractBuilder(),
- [TypeVec(3, TypeAbstractFloat)],
- TypeVec(3, TypeAbstractFloat),
+ [Type.vec(3, Type.abstractFloat)],
+ Type.vec(3, Type.abstractFloat),
t.params,
cases
);
@@ -603,8 +493,8 @@ struct __modf_result_vec3_abstract {
await run(
t,
abstractWholeBuilder(),
- [TypeVec(3, TypeAbstractFloat)],
- TypeVec(3, TypeAbstractFloat),
+ [Type.vec(3, Type.abstractFloat)],
+ Type.vec(3, Type.abstractFloat),
t.params,
cases
);
@@ -628,8 +518,8 @@ struct __modf_result_vec4_abstract {
await run(
t,
abstractFractBuilder(),
- [TypeVec(4, TypeAbstractFloat)],
- TypeVec(4, TypeAbstractFloat),
+ [Type.vec(4, Type.abstractFloat)],
+ Type.vec(4, Type.abstractFloat),
t.params,
cases
);
@@ -653,8 +543,8 @@ struct __modf_result_vec4_abstract {
await run(
t,
abstractWholeBuilder(),
- [TypeVec(4, TypeAbstractFloat)],
- TypeVec(4, TypeAbstractFloat),
+ [Type.vec(4, Type.abstractFloat)],
+ Type.vec(4, Type.abstractFloat),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.cache.ts
new file mode 100644
index 0000000000..7da5a43c22
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.cache.ts
@@ -0,0 +1,25 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_vecN_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateVectorToVectorCases(
+ FP[trait].vectorRange(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ // normalize has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].normalizeInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('normalize', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts
index 615617b448..06be8a125e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/normalize.spec.ts
@@ -1,65 +1,47 @@
export const description = `
Execution tests for the 'normalize' builtin function
-T is AbstractFloat, f32, or f16
+T is abstract-float, f32, or f16
@const fn normalize(e: vecN<T> ) -> vecN<T>
Returns a unit vector in the same direction as e.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { vectorF32Range, vectorF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './normalize.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: f32_vecN_[non_]const
-const f32_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorToVectorCases(
- vectorF32Range(n),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.normalizeInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_vecN_[non_]const
-const f16_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorToVectorCases(
- vectorF16Range(n),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.normalizeInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
+g.test('abstract_float_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec2s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_const');
+ await run(t, abstractFloatBuiltin('normalize'), [Type.vec2af], Type.vec2af, t.params, cases);
+ });
-export const d = makeCaseCache('normalize', {
- ...f32_vec_cases,
- ...f16_vec_cases,
-});
+g.test('abstract_float_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec3s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_const');
+ await run(t, abstractFloatBuiltin('normalize'), [Type.vec3af], Type.vec3af, t.params, cases);
+ });
-g.test('abstract_float')
- .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
- .desc(`abstract float tests`)
- .params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
- )
- .unimplemented();
+g.test('abstract_float_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec4s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_const');
+ await run(t, abstractFloatBuiltin('normalize'), [Type.vec4af], Type.vec4af, t.params, cases);
+ });
g.test('f32_vec2')
.specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
@@ -69,7 +51,7 @@ g.test('f32_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
);
- await run(t, builtin('normalize'), [TypeVec(2, TypeF32)], TypeVec(2, TypeF32), t.params, cases);
+ await run(t, builtin('normalize'), [Type.vec2f], Type.vec2f, t.params, cases);
});
g.test('f32_vec3')
@@ -80,7 +62,7 @@ g.test('f32_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
);
- await run(t, builtin('normalize'), [TypeVec(3, TypeF32)], TypeVec(3, TypeF32), t.params, cases);
+ await run(t, builtin('normalize'), [Type.vec3f], Type.vec3f, t.params, cases);
});
g.test('f32_vec4')
@@ -91,7 +73,7 @@ g.test('f32_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
);
- await run(t, builtin('normalize'), [TypeVec(4, TypeF32)], TypeVec(4, TypeF32), t.params, cases);
+ await run(t, builtin('normalize'), [Type.vec4f], Type.vec4f, t.params, cases);
});
g.test('f16_vec2')
@@ -105,7 +87,7 @@ g.test('f16_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
);
- await run(t, builtin('normalize'), [TypeVec(2, TypeF16)], TypeVec(2, TypeF16), t.params, cases);
+ await run(t, builtin('normalize'), [Type.vec2h], Type.vec2h, t.params, cases);
});
g.test('f16_vec3')
@@ -119,7 +101,7 @@ g.test('f16_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
);
- await run(t, builtin('normalize'), [TypeVec(3, TypeF16)], TypeVec(3, TypeF16), t.params, cases);
+ await run(t, builtin('normalize'), [Type.vec3h], Type.vec3h, t.params, cases);
});
g.test('f16_vec4')
@@ -133,5 +115,5 @@ g.test('f16_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
);
- await run(t, builtin('normalize'), [TypeVec(4, TypeF16)], TypeVec(4, TypeF16), t.params, cases);
+ await run(t, builtin('normalize'), [Type.vec4h], Type.vec4h, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.cache.ts
new file mode 100644
index 0000000000..9cd7824cd9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.cache.ts
@@ -0,0 +1,55 @@
+import { anyOf, skipUndefined } from '../../../../../util/compare.js';
+import { f32, pack2x16float, u32, vec2 } from '../../../../../util/conversion.js';
+import { cartesianProduct, quantizeToF32, scalarF32Range } from '../../../../../util/math.js';
+import { Case } from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// pack2x16float has somewhat unusual behaviour, specifically around how it is
+// supposed to behave when values go OOB and when they are considered to have
+// gone OOB, so has its own bespoke implementation.
+
+/**
+ * @returns a Case for `pack2x16float`
+ * @param param0 first param for the case
+ * @param param1 second param for the case
+ * @param filter_undefined should inputs that cause an undefined expectation be
+ * filtered out, needed for const-eval
+ */
+function makeCase(param0: number, param1: number, filter_undefined: boolean): Case | undefined {
+ param0 = quantizeToF32(param0);
+ param1 = quantizeToF32(param1);
+
+ const results = pack2x16float(param0, param1);
+ if (filter_undefined && results.some(r => r === undefined)) {
+ return undefined;
+ }
+
+ return {
+ input: [vec2(f32(param0), f32(param1))],
+ expected: anyOf(
+ ...results.map(r => (r === undefined ? skipUndefined(undefined) : skipUndefined(u32(r))))
+ ),
+ };
+}
+
+/**
+ * @returns an array of Cases for `pack2x16float`
+ * @param param0s array of inputs to try for the first param
+ * @param param1s array of inputs to try for the second param
+ * @param filter_undefined should inputs that cause an undefined expectation be
+ * filtered out, needed for const-eval
+ */
+function generateCases(param0s: number[], param1s: number[], filter_undefined: boolean): Case[] {
+ return cartesianProduct(param0s, param1s)
+ .map(e => makeCase(e[0], e[1], filter_undefined))
+ .filter((c): c is Case => c !== undefined);
+}
+
+export const d = makeCaseCache('pack2x16float', {
+ f32_const: () => {
+ return generateCases(scalarF32Range(), scalarF32Range(), true);
+ },
+ f32_non_const: () => {
+ return generateCases(scalarF32Range(), scalarF32Range(), false);
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts
index 790e54720c..5ba6993427 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16float.spec.ts
@@ -6,74 +6,14 @@ which is then placed in bits 16 × i through 16 × i + 15 of the result.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { anyOf, skipUndefined } from '../../../../../util/compare.js';
-import {
- f32,
- pack2x16float,
- TypeF32,
- TypeU32,
- TypeVec,
- u32,
- vec2,
-} from '../../../../../util/conversion.js';
-import { cartesianProduct, fullF32Range, quantizeToF32 } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, Case, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
+import { d } from './pack2x16float.cache.js';
export const g = makeTestGroup(GPUTest);
-// pack2x16float has somewhat unusual behaviour, specifically around how it is
-// supposed to behave when values go OOB and when they are considered to have
-// gone OOB, so has its own bespoke implementation.
-
-/**
- * @returns a Case for `pack2x16float`
- * @param param0 first param for the case
- * @param param1 second param for the case
- * @param filter_undefined should inputs that cause an undefined expectation be
- * filtered out, needed for const-eval
- */
-function makeCase(param0: number, param1: number, filter_undefined: boolean): Case | undefined {
- param0 = quantizeToF32(param0);
- param1 = quantizeToF32(param1);
-
- const results = pack2x16float(param0, param1);
- if (filter_undefined && results.some(r => r === undefined)) {
- return undefined;
- }
-
- return {
- input: [vec2(f32(param0), f32(param1))],
- expected: anyOf(
- ...results.map(r => (r === undefined ? skipUndefined(undefined) : skipUndefined(u32(r))))
- ),
- };
-}
-
-/**
- * @returns an array of Cases for `pack2x16float`
- * @param param0s array of inputs to try for the first param
- * @param param1s array of inputs to try for the second param
- * @param filter_undefined should inputs that cause an undefined expectation be
- * filtered out, needed for const-eval
- */
-function generateCases(param0s: number[], param1s: number[], filter_undefined: boolean): Case[] {
- return cartesianProduct(param0s, param1s)
- .map(e => makeCase(e[0], e[1], filter_undefined))
- .filter((c): c is Case => c !== undefined);
-}
-
-export const d = makeCaseCache('pack2x16float', {
- f32_const: () => {
- return generateCases(fullF32Range(), fullF32Range(), true);
- },
- f32_non_const: () => {
- return generateCases(fullF32Range(), fullF32Range(), false);
- },
-});
-
g.test('pack')
.specURL('https://www.w3.org/TR/WGSL/#pack-builtin-functions')
.desc(
@@ -84,5 +24,5 @@ g.test('pack')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('pack2x16float'), [TypeVec(2, TypeF32)], TypeU32, t.params, cases);
+ await run(t, builtin('pack2x16float'), [Type.vec2f], Type.u32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts
index 54bb21f6c6..1bcca2f73f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16snorm.spec.ts
@@ -8,17 +8,10 @@ bits 16 × i through 16 × i + 15 of the result.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
import { kValue } from '../../../../../util/constants.js';
-import {
- f32,
- pack2x16snorm,
- TypeF32,
- TypeU32,
- TypeVec,
- u32,
- vec2,
-} from '../../../../../util/conversion.js';
+import { f32, pack2x16snorm, u32, vec2, Type } from '../../../../../util/conversion.js';
import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js';
-import { allInputSources, Case, run } from '../../expression.js';
+import { Case } from '../../case.js';
+import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -51,5 +44,5 @@ g.test('pack')
];
});
- await run(t, builtin('pack2x16snorm'), [TypeVec(2, TypeF32)], TypeU32, t.params, cases);
+ await run(t, builtin('pack2x16snorm'), [Type.vec2f], Type.u32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts
index a875a9c7e1..334d106482 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack2x16unorm.spec.ts
@@ -8,17 +8,10 @@ bits 16 × i through 16 × i + 15 of the result.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
import { kValue } from '../../../../../util/constants.js';
-import {
- f32,
- pack2x16unorm,
- TypeF32,
- TypeU32,
- TypeVec,
- u32,
- vec2,
-} from '../../../../../util/conversion.js';
+import { f32, pack2x16unorm, u32, vec2, Type } from '../../../../../util/conversion.js';
import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js';
-import { allInputSources, Case, run } from '../../expression.js';
+import { Case } from '../../case.js';
+import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -51,5 +44,5 @@ g.test('pack')
];
});
- await run(t, builtin('pack2x16unorm'), [TypeVec(2, TypeF32)], TypeU32, t.params, cases);
+ await run(t, builtin('pack2x16unorm'), [Type.vec2f], Type.u32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts
index de0463e9fc..fbe362ea45 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8snorm.spec.ts
@@ -8,18 +8,10 @@ bits 8 × i through 8 × i + 7 of the result.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
import { kValue } from '../../../../../util/constants.js';
-import {
- f32,
- pack4x8snorm,
- Scalar,
- TypeF32,
- TypeU32,
- TypeVec,
- u32,
- vec4,
-} from '../../../../../util/conversion.js';
+import { f32, pack4x8snorm, ScalarValue, u32, vec4, Type } from '../../../../../util/conversion.js';
import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js';
-import { allInputSources, Case, run } from '../../expression.js';
+import { Case } from '../../case.js';
+import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -35,7 +27,12 @@ g.test('pack')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const makeCase = (vals: [number, number, number, number]): Case => {
- const vals_f32 = new Array<Scalar>(4) as [Scalar, Scalar, Scalar, Scalar];
+ const vals_f32 = new Array<ScalarValue>(4) as [
+ ScalarValue,
+ ScalarValue,
+ ScalarValue,
+ ScalarValue,
+ ];
for (const idx in vals) {
vals[idx] = quantizeToF32(vals[idx]);
vals_f32[idx] = f32(vals[idx]);
@@ -56,5 +53,5 @@ g.test('pack')
];
});
- await run(t, builtin('pack4x8snorm'), [TypeVec(4, TypeF32)], TypeU32, t.params, cases);
+ await run(t, builtin('pack4x8snorm'), [Type.vec4f], Type.u32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts
index b670e92fbb..c7d62e722b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4x8unorm.spec.ts
@@ -8,18 +8,10 @@ bits 8 × i through 8 × i + 7 of the result.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
import { kValue } from '../../../../../util/constants.js';
-import {
- f32,
- pack4x8unorm,
- Scalar,
- TypeF32,
- TypeU32,
- TypeVec,
- u32,
- vec4,
-} from '../../../../../util/conversion.js';
+import { f32, pack4x8unorm, ScalarValue, u32, vec4, Type } from '../../../../../util/conversion.js';
import { quantizeToF32, vectorF32Range } from '../../../../../util/math.js';
-import { allInputSources, Case, run } from '../../expression.js';
+import { Case } from '../../case.js';
+import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -35,7 +27,12 @@ g.test('pack')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const makeCase = (vals: [number, number, number, number]): Case => {
- const vals_f32 = new Array<Scalar>(4) as [Scalar, Scalar, Scalar, Scalar];
+ const vals_f32 = new Array<ScalarValue>(4) as [
+ ScalarValue,
+ ScalarValue,
+ ScalarValue,
+ ScalarValue,
+ ];
for (const idx in vals) {
vals[idx] = quantizeToF32(vals[idx]);
vals_f32[idx] = f32(vals[idx]);
@@ -56,5 +53,5 @@ g.test('pack')
];
});
- await run(t, builtin('pack4x8unorm'), [TypeVec(4, TypeF32)], TypeU32, t.params, cases);
+ await run(t, builtin('pack4x8unorm'), [Type.vec4f], Type.u32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8.spec.ts
new file mode 100644
index 0000000000..94aa67f0ae
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8.spec.ts
@@ -0,0 +1,69 @@
+export const description = `
+Execution tests for the 'pack4xI8' builtin function
+
+@const fn pack4xI8(e: vec4<i32>) -> u32
+Pack the lower 8 bits of each component of e into a u32 value and drop all the unused bits.
+Component e[i] of the input is mapped to bits (8 * i) through (8 * (i + 7)) of the result.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { u32, toVector, i32, Type } from '../../../../../util/conversion.js';
+import { Case } from '../../case.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('basic')
+ .specURL('https://www.w3.org/TR/WGSL/#pack4xI8-builtin')
+ .desc(
+ `
+@const fn pack4xI8(e: vec4<i32>) -> u32
+ `
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cfg: Config = t.params;
+
+ const pack4xI8 = (vals: readonly [number, number, number, number]) => {
+ const result = new Uint32Array(1);
+ for (let i = 0; i < 4; ++i) {
+ result[0] |= (vals[i] & 0xff) << (i * 8);
+ }
+ return result[0];
+ };
+
+ const testInputs = [
+ [0, 0, 0, 0],
+ [1, 2, 3, 4],
+ [-1, 2, 3, 4],
+ [1, -2, 3, 4],
+ [1, 2, -3, 4],
+ [1, 2, 3, -4],
+ [-1, -2, 3, 4],
+ [-1, 2, -3, 4],
+ [-1, 2, 3, -4],
+ [1, -2, -3, 4],
+ [1, -2, 3, -4],
+ [1, 2, -3, -4],
+ [-1, -2, -3, 4],
+ [-1, -2, 3, -4],
+ [-1, 2, -3, -4],
+ [1, -2, -3, -4],
+ [-1, -2, -3, -4],
+ [127, 128, -128, -129],
+ [128, 128, -128, -128],
+ [32767, 32768, -32768, -32769],
+ ] as const;
+
+ const makeCase = (vals: readonly [number, number, number, number]): Case => {
+ return { input: [toVector(vals, i32)], expected: u32(pack4xI8(vals)) };
+ };
+ const cases: Array<Case> = testInputs.flatMap(v => {
+ return [makeCase(v)];
+ });
+
+ await run(t, builtin('pack4xI8'), [Type.vec4i], Type.u32, cfg, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8Clamp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8Clamp.spec.ts
new file mode 100644
index 0000000000..4968ed1e04
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xI8Clamp.spec.ts
@@ -0,0 +1,73 @@
+export const description = `
+Execution tests for the 'pack4xI8Clamp' builtin function
+
+@const fn pack4xI8Clamp(e: vec4<i32>) -> u32
+Clamp each component of e in the range [-128, 127] and then pack the lower 8 bits of each component
+into a u32 value. Component e[i] of the input is mapped to bits (8 * i) through (8 * (i + 7)) of the
+result.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { u32, toVector, i32, Type } from '../../../../../util/conversion.js';
+import { clamp } from '../../../../../util/math.js';
+import { Case } from '../../case.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('basic')
+ .specURL('https://www.w3.org/TR/WGSL/#pack4xI8Clamp-builtin')
+ .desc(
+ `
+@const fn pack4xI8Clamp(e: vec4<i32>) -> u32
+ `
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cfg: Config = t.params;
+
+ const pack4xI8Clamp = (vals: readonly [number, number, number, number]) => {
+ const result = new Uint32Array(1);
+ for (let i = 0; i < 4; ++i) {
+ const clampedValue = clamp(vals[i], { min: -128, max: 127 });
+ result[0] |= (clampedValue & 0xff) << (i * 8);
+ }
+ return result[0];
+ };
+
+ const testInputs = [
+ [0, 0, 0, 0],
+ [1, 2, 3, 4],
+ [-1, 2, 3, 4],
+ [1, -2, 3, 4],
+ [1, 2, -3, 4],
+ [1, 2, 3, -4],
+ [-1, -2, 3, 4],
+ [-1, 2, -3, 4],
+ [-1, 2, 3, -4],
+ [1, -2, -3, 4],
+ [1, -2, 3, -4],
+ [1, 2, -3, -4],
+ [-1, -2, -3, 4],
+ [-1, -2, 3, -4],
+ [-1, 2, -3, -4],
+ [1, -2, -3, -4],
+ [-1, -2, -3, -4],
+ [126, 127, 128, 129],
+ [-130, -129, -128, -127],
+ [127, 128, -128, -129],
+ [32767, 32768, -32768, -32769],
+ ] as const;
+
+ const makeCase = (vals: readonly [number, number, number, number]): Case => {
+ return { input: [toVector(vals, i32)], expected: u32(pack4xI8Clamp(vals)) };
+ };
+ const cases: Array<Case> = testInputs.flatMap(v => {
+ return [makeCase(v)];
+ });
+
+ await run(t, builtin('pack4xI8Clamp'), [Type.vec4i], Type.u32, cfg, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8.spec.ts
new file mode 100644
index 0000000000..9d08e88d44
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8.spec.ts
@@ -0,0 +1,54 @@
+export const description = `
+Execution tests for the 'pack4xU8' builtin function
+
+@const fn pack4xU8(e: vec4<u32>) -> u32
+Pack the lower 8 bits of each component of e into a u32 value and drop all the unused bits.
+Component e[i] of the input is mapped to bits (8 * i) through (8 * (i + 7)) of the result.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { u32, toVector, Type } from '../../../../../util/conversion.js';
+import { Case } from '../../case.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('basic')
+ .specURL('https://www.w3.org/TR/WGSL/#pack4xU8-builtin')
+ .desc(
+ `
+@const fn pack4xU8(e: vec4<u32>) -> u32
+ `
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cfg: Config = t.params;
+
+ const pack4xU8 = (vals: readonly [number, number, number, number]) => {
+ const result = new Uint32Array(1);
+ for (let i = 0; i < 4; ++i) {
+ result[0] |= (vals[i] & 0xff) << (i * 8);
+ }
+ return result[0];
+ };
+
+ const testInputs = [
+ [0, 0, 0, 0],
+ [1, 2, 3, 4],
+ [255, 255, 255, 255],
+ [254, 255, 256, 257],
+ [65535, 65536, 255, 254],
+ ] as const;
+
+ const makeCase = (vals: readonly [number, number, number, number]): Case => {
+ return { input: [toVector(vals, u32)], expected: u32(pack4xU8(vals)) };
+ };
+ const cases: Array<Case> = testInputs.flatMap(v => {
+ return [makeCase(v)];
+ });
+
+ await run(t, builtin('pack4xU8'), [Type.vec4u], Type.u32, cfg, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8Clamp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8Clamp.spec.ts
new file mode 100644
index 0000000000..ffecf9b877
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pack4xU8Clamp.spec.ts
@@ -0,0 +1,57 @@
+export const description = `
+Execution tests for the 'pack4xU8Clamp' builtin function
+
+@const fn pack4xU8Clamp(e: vec4<u32>) -> u32
+Clamp each component of e in the range of [0, 255] and then pack the lower 8 bits of each component
+into a u32 value. Component e[i] of the input is mapped to bits (8 * i) through (8 * (i + 7)) of the
+result.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { u32, toVector, Type } from '../../../../../util/conversion.js';
+import { clamp } from '../../../../../util/math.js';
+import { Case } from '../../case.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('basic')
+ .specURL('https://www.w3.org/TR/WGSL/#pack4xU8Clamp-builtin')
+ .desc(
+ `
+@const fn pack4xU8Clamp(e: vec4<u32>) -> u32
+ `
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cfg: Config = t.params;
+
+ const pack4xU8Clamp = (vals: readonly [number, number, number, number]) => {
+ const result = new Uint32Array(1);
+ for (let i = 0; i < 4; ++i) {
+ const clampedValue = clamp(vals[i], { min: 0, max: 255 });
+ result[0] |= clampedValue << (i * 8);
+ }
+ return result[0];
+ };
+
+ const testInputs = [
+ [0, 0, 0, 0],
+ [1, 2, 3, 4],
+ [255, 255, 255, 255],
+ [254, 255, 256, 257],
+ [65535, 65536, 255, 254],
+ ] as const;
+
+ const makeCase = (vals: readonly [number, number, number, number]): Case => {
+ return { input: [toVector(vals, u32)], expected: u32(pack4xU8Clamp(vals)) };
+ };
+ const cases: Array<Case> = testInputs.flatMap(v => {
+ return [makeCase(v)];
+ });
+
+ await run(t, builtin('pack4xU8Clamp'), [Type.vec4u], Type.u32, cfg, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.cache.ts
new file mode 100644
index 0000000000..54777e702f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.cache.ts
@@ -0,0 +1,24 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarPairToIntervalCases(
+ FP[trait].scalarRange(),
+ FP[trait].scalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // pow has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].powInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('pow', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts
index f9b4fe1cfa..84e9649c96 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/pow.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'pow' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn pow(e1: T ,e2: T ) -> T
Returns e1 raised to the power e2. Component-wise when T is a vector.
@@ -9,58 +9,33 @@ Returns e1 raised to the power e2. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './pow.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('pow', {
- f32_const: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- fullF32Range(),
- fullF32Range(),
- 'finite',
- FP.f32.powInterval
- );
- },
- f32_non_const: () => {
- return FP.f32.generateScalarPairToIntervalCases(
- fullF32Range(),
- fullF32Range(),
- 'unfiltered',
- FP.f32.powInterval
- );
- },
- f16_const: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- fullF16Range(),
- fullF16Range(),
- 'finite',
- FP.f16.powInterval
- );
- },
- f16_non_const: () => {
- return FP.f16.generateScalarPairToIntervalCases(
- fullF16Range(),
- fullF16Range(),
- 'unfiltered',
- FP.f16.powInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('pow'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -70,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('pow'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('pow'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -84,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('pow'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('pow'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.cache.ts
new file mode 100644
index 0000000000..91aa845d29
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.cache.ts
@@ -0,0 +1,41 @@
+import { kValue } from '../../../../../util/constants.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { scalarF16Range, scalarF32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+export const d = makeCaseCache('quantizeToF16', {
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ kValue.f16.negative.min,
+ kValue.f16.negative.max,
+ kValue.f16.negative.subnormal.min,
+ kValue.f16.negative.subnormal.max,
+ kValue.f16.positive.subnormal.min,
+ kValue.f16.positive.subnormal.max,
+ kValue.f16.positive.min,
+ kValue.f16.positive.max,
+ ...scalarF16Range(),
+ ],
+ 'finite',
+ FP.f32.quantizeToF16Interval
+ );
+ },
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [
+ kValue.f16.negative.min,
+ kValue.f16.negative.max,
+ kValue.f16.negative.subnormal.min,
+ kValue.f16.negative.subnormal.max,
+ kValue.f16.positive.subnormal.min,
+ kValue.f16.positive.subnormal.max,
+ kValue.f16.positive.min,
+ kValue.f16.positive.max,
+ ...scalarF32Range(),
+ ],
+ 'unfiltered',
+ FP.f32.quantizeToF16Interval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts
index b37d4c5afb..0aa9669e93 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/quantizeToF16.spec.ts
@@ -10,54 +10,14 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { kValue } from '../../../../../util/constants.js';
-import { TypeF32 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF16Range, fullF32Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
+import { d } from './quantizeToF16.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('quantizeToF16', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- kValue.f16.negative.min,
- kValue.f16.negative.max,
- kValue.f16.negative.subnormal.min,
- kValue.f16.negative.subnormal.max,
- kValue.f16.positive.subnormal.min,
- kValue.f16.positive.subnormal.max,
- kValue.f16.positive.min,
- kValue.f16.positive.max,
- ...fullF16Range(),
- ],
- 'finite',
- FP.f32.quantizeToF16Interval
- );
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- kValue.f16.negative.min,
- kValue.f16.negative.max,
- kValue.f16.negative.subnormal.min,
- kValue.f16.negative.subnormal.max,
- kValue.f16.positive.subnormal.min,
- kValue.f16.positive.subnormal.max,
- kValue.f16.positive.min,
- kValue.f16.positive.max,
- ...fullF32Range(),
- ],
- 'unfiltered',
- FP.f32.quantizeToF16Interval
- );
- },
-});
-
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`f32 tests`)
@@ -66,5 +26,5 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('quantizeToF16'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('quantizeToF16'), [Type.f32], Type.f32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.cache.ts
new file mode 100644
index 0000000000..8ed0fbbd2b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.cache.ts
@@ -0,0 +1,18 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ trait !== 'abstract' ? 'unfiltered' : 'finite',
+ // radians has an inherited accuracy, so abstract is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].radiansInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('radians', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts
index 63ae45b656..a405807ec0 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/radians.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'radians' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn radians(e1: T ) -> T
Converts degrees to radians, approximating e1 * π / 180.
@@ -10,40 +10,14 @@ Component-wise when T is a vector
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF16Range, fullF32Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './radians.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('radians', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- fullF32Range(),
- 'unfiltered',
- FP.f32.radiansInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- fullF16Range(),
- 'unfiltered',
- FP.f16.radiansInterval
- );
- },
- abstract: () => {
- return FP.abstract.generateScalarToIntervalCases(
- fullF16Range(),
- 'unfiltered',
- FP.abstract.radiansInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
@@ -56,9 +30,9 @@ g.test('abstract_float')
const cases = await d.get('abstract');
await run(
t,
- abstractBuiltin('radians'),
- [TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBuiltin('radians'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -72,7 +46,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('radians'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('radians'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -86,5 +60,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('radians'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('radians'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.cache.ts
new file mode 100644
index 0000000000..ca57226f3a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.cache.ts
@@ -0,0 +1,26 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_vecN_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateVectorPairToVectorCases(
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseVectorRange(dim),
+ nonConst ? 'unfiltered' : 'finite',
+ // reflect has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].reflectInterval
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('reflect', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts
index 2614c4e686..e36558d30c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reflect.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'reflect' builtin function
-T is vecN<AbstractFloat>, vecN<f32>, or vecN<f16>
+T is vecN<Type.abstractFloat>, vecN<f32>, or vecN<f16>
@const fn reflect(e1: T, e2: T ) -> T
For the incident vector e1 and surface orientation e2, returns the reflection
direction e1-2*dot(e2,e1)*e2.
@@ -9,58 +9,61 @@ direction e1-2*dot(e2,e1)*e2.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { sparseVectorF32Range, sparseVectorF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './reflect.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: f32_vecN_[non_]const
-const f32_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateVectorPairToVectorCases(
- sparseVectorF32Range(n),
- sparseVectorF32Range(n),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.reflectInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_vecN_[non_]const
-const f16_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateVectorPairToVectorCases(
- sparseVectorF16Range(n),
- sparseVectorF16Range(n),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.reflectInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
+g.test('abstract_float_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec2s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_const');
+ await run(
+ t,
+ abstractFloatBuiltin('reflect'),
+ [Type.vec2af, Type.vec2af],
+ Type.vec2af,
+ t.params,
+ cases
+ );
+ });
-export const d = makeCaseCache('reflect', {
- ...f32_vec_cases,
- ...f16_vec_cases,
-});
+g.test('abstract_float_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec3s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_const');
+ await run(
+ t,
+ abstractFloatBuiltin('reflect'),
+ [Type.vec3af, Type.vec3af],
+ Type.vec3af,
+ t.params,
+ cases
+ );
+ });
-g.test('abstract_float')
- .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
- .desc(`abstract float tests`)
- .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const))
- .unimplemented();
+g.test('abstract_float_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec4s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_const');
+ await run(
+ t,
+ abstractFloatBuiltin('reflect'),
+ [Type.vec4af, Type.vec4af],
+ Type.vec4af,
+ t.params,
+ cases
+ );
+ });
g.test('f32_vec2')
.specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
@@ -70,14 +73,7 @@ g.test('f32_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec2_const' : 'f32_vec2_non_const'
);
- await run(
- t,
- builtin('reflect'),
- [TypeVec(2, TypeF32), TypeVec(2, TypeF32)],
- TypeVec(2, TypeF32),
- t.params,
- cases
- );
+ await run(t, builtin('reflect'), [Type.vec2f, Type.vec2f], Type.vec2f, t.params, cases);
});
g.test('f32_vec3')
@@ -88,14 +84,7 @@ g.test('f32_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec3_const' : 'f32_vec3_non_const'
);
- await run(
- t,
- builtin('reflect'),
- [TypeVec(3, TypeF32), TypeVec(3, TypeF32)],
- TypeVec(3, TypeF32),
- t.params,
- cases
- );
+ await run(t, builtin('reflect'), [Type.vec3f, Type.vec3f], Type.vec3f, t.params, cases);
});
g.test('f32_vec4')
@@ -106,14 +95,7 @@ g.test('f32_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f32_vec4_const' : 'f32_vec4_non_const'
);
- await run(
- t,
- builtin('reflect'),
- [TypeVec(4, TypeF32), TypeVec(4, TypeF32)],
- TypeVec(4, TypeF32),
- t.params,
- cases
- );
+ await run(t, builtin('reflect'), [Type.vec4f, Type.vec4f], Type.vec4f, t.params, cases);
});
g.test('f16_vec2')
@@ -127,14 +109,7 @@ g.test('f16_vec2')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec2_const' : 'f16_vec2_non_const'
);
- await run(
- t,
- builtin('reflect'),
- [TypeVec(2, TypeF16), TypeVec(2, TypeF16)],
- TypeVec(2, TypeF16),
- t.params,
- cases
- );
+ await run(t, builtin('reflect'), [Type.vec2h, Type.vec2h], Type.vec2h, t.params, cases);
});
g.test('f16_vec3')
@@ -148,14 +123,7 @@ g.test('f16_vec3')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec3_const' : 'f16_vec3_non_const'
);
- await run(
- t,
- builtin('reflect'),
- [TypeVec(3, TypeF16), TypeVec(3, TypeF16)],
- TypeVec(3, TypeF16),
- t.params,
- cases
- );
+ await run(t, builtin('reflect'), [Type.vec3h, Type.vec3h], Type.vec3h, t.params, cases);
});
g.test('f16_vec4')
@@ -169,12 +137,5 @@ g.test('f16_vec4')
const cases = await d.get(
t.params.inputSource === 'const' ? 'f16_vec4_const' : 'f16_vec4_non_const'
);
- await run(
- t,
- builtin('reflect'),
- [TypeVec(4, TypeF16), TypeVec(4, TypeF16)],
- TypeVec(4, TypeF16),
- t.params,
- cases
- );
+ await run(t, builtin('reflect'), [Type.vec4h, Type.vec4h], Type.vec4h, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.cache.ts
new file mode 100644
index 0000000000..a759f5b669
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.cache.ts
@@ -0,0 +1,116 @@
+import { ROArrayArray } from '../../../../../../common/util/types.js';
+import { toVector } from '../../../../../util/conversion.js';
+import { FP, FPKind } from '../../../../../util/floating_point.js';
+import { Case, selectNCases } from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+import { IntervalFilter } from '../../interval_filter.js';
+
+// Using a bespoke implementation of make*Case and generate*Cases here
+// since refract is the only builtin with the API signature
+// (vec, vec, scalar) -> vec
+
+/**
+ * @returns a Case for `refract`
+ * @param argumentKind what kind of floating point numbers being operated on
+ * @param parameterKind what kind of floating point operation should be performed,
+ * should be the same as argumentKind, except for abstract
+ * @param i the `i` param for the case
+ * @param s the `s` param for the case
+ * @param r the `r` param for the case
+ * @param check what interval checking to apply
+ * */
+function makeCase(
+ argumentKind: FPKind,
+ parameterKind: FPKind,
+ i: readonly number[],
+ s: readonly number[],
+ r: number,
+ check: IntervalFilter
+): Case | undefined {
+ const fp = FP[argumentKind];
+ i = i.map(fp.quantize);
+ s = s.map(fp.quantize);
+ r = fp.quantize(r);
+
+ const vectors = FP[parameterKind].refractInterval(i, s, r);
+ if (check === 'finite' && vectors.some(e => !e.isFinite())) {
+ return undefined;
+ }
+
+ return {
+ input: [toVector(i, fp.scalarBuilder), toVector(s, fp.scalarBuilder), fp.scalarBuilder(r)],
+ expected: vectors,
+ };
+}
+
+/**
+ * @returns an array of Cases for `refract`
+ * @param argumentKind what kind of floating point numbers being operated on
+ * @param parameterKind what kind of floating point operation should be performed,
+ * should be the same as argumentKind, except for abstract
+ * @param param_is array of inputs to try for the `i` param
+ * @param param_ss array of inputs to try for the `s` param
+ * @param param_rs array of inputs to try for the `r` param
+ * @param check what interval checking to apply
+ */
+function generateCases(
+ argumentKind: FPKind,
+ parameterKind: FPKind,
+ param_is: ROArrayArray<number>,
+ param_ss: ROArrayArray<number>,
+ param_rs: readonly number[],
+ check: IntervalFilter
+): Case[] {
+ // Cannot use `cartesianProduct` here due to heterogeneous param types
+ return param_is
+ .flatMap(i => {
+ return param_ss.flatMap(s => {
+ return param_rs.map(r => {
+ return makeCase(argumentKind, parameterKind, i, s, r, check);
+ });
+ });
+ })
+ .filter((c): c is Case => c !== undefined);
+}
+
+// Cases: [f32|f16|abstract]_vecN_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(dim =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_vec${dim}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ if (trait !== 'abstract') {
+ return generateCases(
+ trait,
+ trait,
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseScalarRange(),
+ nonConst ? 'unfiltered' : 'finite'
+ );
+ } else {
+ // Restricting the number of cases, because a vector of abstract floats needs to be returned, which is costly.
+ return selectNCases(
+ 'faceForward',
+ 20,
+ generateCases(
+ trait,
+ // refract has an inherited accuracy, so is only expected to be as accurate as f32
+ 'f32',
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseVectorRange(dim),
+ FP[trait].sparseScalarRange(),
+ nonConst ? 'unfiltered' : 'finite'
+ )
+ );
+ }
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('refract', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts
index be1a76b437..5b51f30eee 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/refract.spec.ts
@@ -2,7 +2,7 @@ export const description = `
Execution tests for the 'refract' builtin function
T is vecN<I>
-I is AbstractFloat, f32, or f16
+I is abstract-float, f32, or f16
@const fn refract(e1: T ,e2: T ,e3: I ) -> T
For the incident vector e1 and surface normal e2, and the ratio of indices of
refraction e3, let k = 1.0 -e3*e3* (1.0 - dot(e2,e1) * dot(e2,e1)).
@@ -11,129 +11,62 @@ vector e3*e1- (e3* dot(e2,e1) + sqrt(k)) *e2.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
-import { ROArrayArray } from '../../../../../../common/util/types.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { toVector, TypeF32, TypeF16, TypeVec } from '../../../../../util/conversion.js';
-import { FP, FPKind } from '../../../../../util/floating_point.js';
-import {
- sparseVectorF32Range,
- sparseVectorF16Range,
- sparseF32Range,
- sparseF16Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, Case, IntervalFilter, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './refract.cache.js';
export const g = makeTestGroup(GPUTest);
-// Using a bespoke implementation of make*Case and generate*Cases here
-// since refract is the only builtin with the API signature
-// (vec, vec, scalar) -> vec
-
-/**
- * @returns a Case for `refract`
- * @param kind what type of floating point numbers to operate on
- * @param i the `i` param for the case
- * @param s the `s` param for the case
- * @param r the `r` param for the case
- * @param check what interval checking to apply
- * */
-function makeCase(
- kind: FPKind,
- i: readonly number[],
- s: readonly number[],
- r: number,
- check: IntervalFilter
-): Case | undefined {
- const fp = FP[kind];
- i = i.map(fp.quantize);
- s = s.map(fp.quantize);
- r = fp.quantize(r);
-
- const vectors = fp.refractInterval(i, s, r);
- if (check === 'finite' && vectors.some(e => !e.isFinite())) {
- return undefined;
- }
-
- return {
- input: [toVector(i, fp.scalarBuilder), toVector(s, fp.scalarBuilder), fp.scalarBuilder(r)],
- expected: fp.refractInterval(i, s, r),
- };
-}
-
-/**
- * @returns an array of Cases for `refract`
- * @param kind what type of floating point numbers to operate on
- * @param param_is array of inputs to try for the `i` param
- * @param param_ss array of inputs to try for the `s` param
- * @param param_rs array of inputs to try for the `r` param
- * @param check what interval checking to apply
- */
-function generateCases(
- kind: FPKind,
- param_is: ROArrayArray<number>,
- param_ss: ROArrayArray<number>,
- param_rs: readonly number[],
- check: IntervalFilter
-): Case[] {
- // Cannot use `cartesianProduct` here due to heterogeneous param types
- return param_is
- .flatMap(i => {
- return param_ss.flatMap(s => {
- return param_rs.map(r => {
- return makeCase(kind, i, s, r, check);
- });
- });
- })
- .filter((c): c is Case => c !== undefined);
-}
-
-// Cases: f32_vecN_[non_]const
-const f32_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f32_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return generateCases(
- 'f32',
- sparseVectorF32Range(n),
- sparseVectorF32Range(n),
- sparseF32Range(),
- nonConst ? 'unfiltered' : 'finite'
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_vecN_[non_]const
-const f16_vec_cases = ([2, 3, 4] as const)
- .flatMap(n =>
- ([true, false] as const).map(nonConst => ({
- [`f16_vec${n}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return generateCases(
- 'f16',
- sparseVectorF16Range(n),
- sparseVectorF16Range(n),
- sparseF16Range(),
- nonConst ? 'unfiltered' : 'finite'
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
+g.test('abstract_float_vec2')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec2s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec2_const');
+ await run(
+ t,
+ abstractFloatBuiltin('refract'),
+ [Type.vec2af, Type.vec2af, Type.abstractFloat],
+ Type.vec2af,
+ t.params,
+ cases
+ );
+ });
-export const d = makeCaseCache('refract', {
- ...f32_vec_cases,
- ...f16_vec_cases,
-});
+g.test('abstract_float_vec3')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec3s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec3_const');
+ await run(
+ t,
+ abstractFloatBuiltin('refract'),
+ [Type.vec3af, Type.vec3af, Type.abstractFloat],
+ Type.vec3af,
+ t.params,
+ cases
+ );
+ });
-g.test('abstract_float')
- .specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
- .desc(`abstract float tests`)
- .params(u => u.combine('inputSource', allInputSources).combine('vectorize', [2, 3, 4] as const))
- .unimplemented();
+g.test('abstract_float_vec4')
+ .specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
+ .desc(`abstract float tests using vec4s`)
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract_vec4_const');
+ await run(
+ t,
+ abstractFloatBuiltin('refract'),
+ [Type.vec4af, Type.vec4af, Type.abstractFloat],
+ Type.vec4af,
+ t.params,
+ cases
+ );
+ });
g.test('f32_vec2')
.specURL('https://www.w3.org/TR/WGSL/#numeric-builtin-functions')
@@ -146,8 +79,8 @@ g.test('f32_vec2')
await run(
t,
builtin('refract'),
- [TypeVec(2, TypeF32), TypeVec(2, TypeF32), TypeF32],
- TypeVec(2, TypeF32),
+ [Type.vec2f, Type.vec2f, Type.f32],
+ Type.vec2f,
t.params,
cases
);
@@ -164,8 +97,8 @@ g.test('f32_vec3')
await run(
t,
builtin('refract'),
- [TypeVec(3, TypeF32), TypeVec(3, TypeF32), TypeF32],
- TypeVec(3, TypeF32),
+ [Type.vec3f, Type.vec3f, Type.f32],
+ Type.vec3f,
t.params,
cases
);
@@ -182,8 +115,8 @@ g.test('f32_vec4')
await run(
t,
builtin('refract'),
- [TypeVec(4, TypeF32), TypeVec(4, TypeF32), TypeF32],
- TypeVec(4, TypeF32),
+ [Type.vec4f, Type.vec4f, Type.f32],
+ Type.vec4f,
t.params,
cases
);
@@ -203,8 +136,8 @@ g.test('f16_vec2')
await run(
t,
builtin('refract'),
- [TypeVec(2, TypeF16), TypeVec(2, TypeF16), TypeF16],
- TypeVec(2, TypeF16),
+ [Type.vec2h, Type.vec2h, Type.f16],
+ Type.vec2h,
t.params,
cases
);
@@ -224,8 +157,8 @@ g.test('f16_vec3')
await run(
t,
builtin('refract'),
- [TypeVec(3, TypeF16), TypeVec(3, TypeF16), TypeF16],
- TypeVec(3, TypeF16),
+ [Type.vec3h, Type.vec3h, Type.f16],
+ Type.vec3h,
t.params,
cases
);
@@ -245,8 +178,8 @@ g.test('f16_vec4')
await run(
t,
builtin('refract'),
- [TypeVec(4, TypeF16), TypeVec(4, TypeF16), TypeF16],
- TypeVec(4, TypeF16),
+ [Type.vec4h, Type.vec4h, Type.f16],
+ Type.vec4h,
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts
index 6acb359822..e235e62a52 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/reverseBits.spec.ts
@@ -10,7 +10,7 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeU32, u32Bits, TypeI32, i32Bits } from '../../../../../util/conversion.js';
+import { u32Bits, i32Bits, Type } from '../../../../../util/conversion.js';
import { allInputSources, Config, run } from '../../expression.js';
import { builtin } from './builtin.js';
@@ -26,7 +26,7 @@ g.test('u32')
.fn(async t => {
const cfg: Config = t.params;
// prettier-ignore
- await run(t, builtin('reverseBits'), [TypeU32], TypeU32, cfg, [
+ await run(t, builtin('reverseBits'), [Type.u32], Type.u32, cfg, [
// Zero
{ input: u32Bits(0b00000000000000000000000000000000), expected: u32Bits(0b00000000000000000000000000000000) },
@@ -142,7 +142,7 @@ g.test('i32')
.fn(async t => {
const cfg: Config = t.params;
// prettier-ignore
- await run(t, builtin('reverseBits'), [TypeI32], TypeI32, cfg, [
+ await run(t, builtin('reverseBits'), [Type.i32], Type.i32, cfg, [
// Zero
{ input: i32Bits(0b00000000000000000000000000000000), expected: i32Bits(0b00000000000000000000000000000000) },
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.cache.ts
new file mode 100644
index 0000000000..e5383b2075
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.cache.ts
@@ -0,0 +1,24 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// See https://github.com/gpuweb/cts/issues/2766 for details
+const kIssue2766Value = {
+ abstract: 0x8000_0000_0000_0000,
+ f32: 0x8000_0000,
+ f16: 0x8000,
+};
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ [kIssue2766Value[trait], ...FP[trait].scalarRange()],
+ 'unfiltered',
+ FP[trait].roundInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('round', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts
index bd40ed4b2a..eeaf41b381 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/round.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'round' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn round(e: T) -> T
Result is the integer k nearest to e, as a floating point value.
@@ -12,46 +12,33 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './round.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('round', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- 0x80000000, // https://github.com/gpuweb/cts/issues/2766,
- ...fullF32Range(),
- ],
- 'unfiltered',
- FP.f32.roundInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- [
- 0x8000, // https://github.com/gpuweb/cts/issues/2766
- ...fullF16Range(),
- ],
- 'unfiltered',
- FP.f16.roundInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('round'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -61,7 +48,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('round'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('round'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -75,5 +62,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('round'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('round'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.cache.ts
new file mode 100644
index 0000000000..4a4ffeee30
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.cache.ts
@@ -0,0 +1,18 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ [...linearRange(0.0, 1.0, 20), ...FP[trait].scalarRange()],
+ 'unfiltered',
+ FP[trait].saturateInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('saturate', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts
index 2f16502921..79c61e4eec 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/saturate.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'saturate' builtin function
-S is AbstractFloat, f32, or f16
+S is abstract-float, f32, or f16
T is S or vecN<S>
@const fn saturate(e: T) -> T
Returns clamp(e, 0.0, 1.0). Component-wise when T is a vector.
@@ -9,52 +9,14 @@ Returns clamp(e, 0.0, 1.0). Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF16Range, fullF32Range, fullF64Range, linearRange } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './saturate.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('saturate', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- // Non-clamped values
- ...linearRange(0.0, 1.0, 20),
- ...fullF32Range(),
- ],
- 'unfiltered',
- FP.f32.saturateInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- [
- // Non-clamped values
- ...linearRange(0.0, 1.0, 20),
- ...fullF16Range(),
- ],
- 'unfiltered',
- FP.f16.saturateInterval
- );
- },
- abstract: () => {
- return FP.abstract.generateScalarToIntervalCases(
- [
- // Non-clamped values
- ...linearRange(0.0, 1.0, 20),
- ...fullF64Range(),
- ],
- 'unfiltered',
- FP.abstract.saturateInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
@@ -67,9 +29,9 @@ g.test('abstract_float')
const cases = await d.get('abstract');
await run(
t,
- abstractBuiltin('saturate'),
- [TypeAbstractFloat],
- TypeAbstractFloat,
+ abstractFloatBuiltin('saturate'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
t.params,
cases
);
@@ -82,7 +44,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('saturate'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('saturate'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -96,5 +58,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('saturate'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('saturate'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts
index c64f989f42..63accbc2d4 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/select.spec.ts
@@ -14,12 +14,6 @@ import { makeTestGroup } from '../../../../../../common/framework/test_group.js'
import { GPUTest } from '../../../../../gpu_test.js';
import {
VectorType,
- TypeVec,
- TypeBool,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeU32,
f32,
f16,
i32,
@@ -31,11 +25,14 @@ import {
vec3,
vec4,
abstractFloat,
- TypeAbstractFloat,
+ abstractInt,
+ ScalarValue,
+ Type,
} from '../../../../../util/conversion.js';
-import { run, CaseList, allInputSources } from '../../expression.js';
+import { Case } from '../../case.js';
+import { run, allInputSources } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js';
export const g = makeTestGroup(GPUTest);
@@ -43,32 +40,47 @@ function makeBool(n: number) {
return bool((n & 1) === 1);
}
-type scalarKind = 'b' | 'af' | 'f' | 'h' | 'i' | 'u';
+type scalarKind = 'b' | 'af' | 'f' | 'h' | 'ai' | 'i' | 'u';
const dataType = {
b: {
- type: TypeBool,
- constructor: makeBool,
+ type: Type.bool,
+ scalar_builder: makeBool,
+ shader_builder: builtin('select'),
},
af: {
- type: TypeAbstractFloat,
- constructor: abstractFloat,
+ type: Type.abstractFloat,
+ scalar_builder: abstractFloat,
+ shader_builder: abstractFloatBuiltin('select'),
},
f: {
- type: TypeF32,
- constructor: f32,
+ type: Type.f32,
+ scalar_builder: f32,
+ shader_builder: builtin('select'),
},
h: {
- type: TypeF16,
- constructor: f16,
+ type: Type.f16,
+ scalar_builder: f16,
+ shader_builder: builtin('select'),
+ },
+ ai: {
+ type: Type.abstractInt,
+ // Only ints are used in the tests below, so the conversion to bigint will
+ // be safe. If a non-int is passed in this will Error.
+ scalar_builder: (v: number): ScalarValue => {
+ return abstractInt(BigInt(v));
+ },
+ shader_builder: abstractIntBuiltin('select'),
},
i: {
- type: TypeI32,
- constructor: i32,
+ type: Type.i32,
+ scalar_builder: i32,
+ shader_builder: builtin('select'),
},
u: {
- type: TypeU32,
- constructor: u32,
+ type: Type.u32,
+ scalar_builder: u32,
+ shader_builder: builtin('select'),
},
};
@@ -78,7 +90,7 @@ g.test('scalar')
.params(u =>
u
.combine('inputSource', allInputSources)
- .combine('component', ['b', 'af', 'f', 'h', 'i', 'u'] as const)
+ .combine('component', ['b', 'af', 'f', 'h', 'ai', 'i', 'u'] as const)
.combine('overload', ['scalar', 'vec2', 'vec3', 'vec4'] as const)
)
.beforeAllSubcases(t => {
@@ -86,10 +98,11 @@ g.test('scalar')
t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
}
t.skipIf(t.params.component === 'af' && t.params.inputSource !== 'const');
+ t.skipIf(t.params.component === 'ai' && t.params.inputSource !== 'const');
})
.fn(async t => {
- const componentType = dataType[t.params.component as scalarKind].type;
- const cons = dataType[t.params.component as scalarKind].constructor;
+ const componentType = dataType[t.params.component].type;
+ const scalar_builder = dataType[t.params.component].scalar_builder;
// Create the scalar values that will be selected from, either as scalars
// or vectors.
@@ -97,39 +110,40 @@ g.test('scalar')
// Each boolean will select between c[k] and c[k+4]. Those values must
// always compare as different. The tricky case is boolean, where the parity
// has to be different, i.e. c[k]-c[k+4] must be odd.
- const c = [0, 1, 2, 3, 5, 6, 7, 8].map(i => cons(i));
+ const scalars = [0, 1, 2, 3, 5, 6, 7, 8].map(i => scalar_builder(i));
+
// Now form vectors that will have different components from each other.
- const v2a = vec2(c[0], c[1]);
- const v2b = vec2(c[4], c[5]);
- const v3a = vec3(c[0], c[1], c[2]);
- const v3b = vec3(c[4], c[5], c[6]);
- const v4a = vec4(c[0], c[1], c[2], c[3]);
- const v4b = vec4(c[4], c[5], c[6], c[7]);
+ const v2a = vec2(scalars[0], scalars[1]);
+ const v2b = vec2(scalars[4], scalars[5]);
+ const v3a = vec3(scalars[0], scalars[1], scalars[2]);
+ const v3b = vec3(scalars[4], scalars[5], scalars[6]);
+ const v4a = vec4(scalars[0], scalars[1], scalars[2], scalars[3]);
+ const v4b = vec4(scalars[4], scalars[5], scalars[6], scalars[7]);
const overloads = {
scalar: {
type: componentType,
cases: [
- { input: [c[0], c[1], False], expected: c[0] },
- { input: [c[0], c[1], True], expected: c[1] },
+ { input: [scalars[0], scalars[1], False], expected: scalars[0] },
+ { input: [scalars[0], scalars[1], True], expected: scalars[1] },
],
},
vec2: {
- type: TypeVec(2, componentType),
+ type: Type.vec(2, componentType),
cases: [
{ input: [v2a, v2b, False], expected: v2a },
{ input: [v2a, v2b, True], expected: v2b },
],
},
vec3: {
- type: TypeVec(3, componentType),
+ type: Type.vec(3, componentType),
cases: [
{ input: [v3a, v3b, False], expected: v3a },
{ input: [v3a, v3b, True], expected: v3b },
],
},
vec4: {
- type: TypeVec(4, componentType),
+ type: Type.vec(4, componentType),
cases: [
{ input: [v4a, v4b, False], expected: v4a },
{ input: [v4a, v4b, True], expected: v4b },
@@ -140,8 +154,8 @@ g.test('scalar')
await run(
t,
- t.params.component === 'af' ? abstractBuiltin('select') : builtin('select'),
- [overload.type, overload.type, TypeBool],
+ dataType[t.params.component as scalarKind].shader_builder,
+ [overload.type, overload.type, Type.bool],
overload.type,
t.params,
overload.cases
@@ -154,7 +168,7 @@ g.test('vector')
.params(u =>
u
.combine('inputSource', allInputSources)
- .combine('component', ['b', 'af', 'f', 'h', 'i', 'u'] as const)
+ .combine('component', ['b', 'af', 'f', 'h', 'ai', 'i', 'u'] as const)
.combine('overload', ['vec2', 'vec3', 'vec4'] as const)
)
.beforeAllSubcases(t => {
@@ -162,29 +176,30 @@ g.test('vector')
t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
}
t.skipIf(t.params.component === 'af' && t.params.inputSource !== 'const');
+ t.skipIf(t.params.component === 'ai' && t.params.inputSource !== 'const');
})
.fn(async t => {
- const componentType = dataType[t.params.component as scalarKind].type;
- const cons = dataType[t.params.component as scalarKind].constructor;
+ const componentType = dataType[t.params.component].type;
+ const scalar_builder = dataType[t.params.component].scalar_builder;
// Create the scalar values that will be selected from.
//
// Each boolean will select between c[k] and c[k+4]. Those values must
// always compare as different. The tricky case is boolean, where the parity
// has to be different, i.e. c[k]-c[k+4] must be odd.
- const c = [0, 1, 2, 3, 5, 6, 7, 8].map(i => cons(i));
+ const scalars = [0, 1, 2, 3, 5, 6, 7, 8].map(i => scalar_builder(i));
const T = True;
const F = False;
- let tests: { dataType: VectorType; boolType: VectorType; cases: CaseList };
+ let tests: { dataType: VectorType; boolType: VectorType; cases: Case[] };
switch (t.params.overload) {
case 'vec2': {
- const a = vec2(c[0], c[1]);
- const b = vec2(c[4], c[5]);
+ const a = vec2(scalars[0], scalars[1]);
+ const b = vec2(scalars[4], scalars[5]);
tests = {
- dataType: TypeVec(2, componentType),
- boolType: TypeVec(2, TypeBool),
+ dataType: Type.vec(2, componentType),
+ boolType: Type.vec(2, Type.bool),
cases: [
{ input: [a, b, vec2(F, F)], expected: vec2(a.x, a.y) },
{ input: [a, b, vec2(F, T)], expected: vec2(a.x, b.y) },
@@ -195,11 +210,11 @@ g.test('vector')
break;
}
case 'vec3': {
- const a = vec3(c[0], c[1], c[2]);
- const b = vec3(c[4], c[5], c[6]);
+ const a = vec3(scalars[0], scalars[1], scalars[2]);
+ const b = vec3(scalars[4], scalars[5], scalars[6]);
tests = {
- dataType: TypeVec(3, componentType),
- boolType: TypeVec(3, TypeBool),
+ dataType: Type.vec(3, componentType),
+ boolType: Type.vec(3, Type.bool),
cases: [
{ input: [a, b, vec3(F, F, F)], expected: vec3(a.x, a.y, a.z) },
{ input: [a, b, vec3(F, F, T)], expected: vec3(a.x, a.y, b.z) },
@@ -214,11 +229,11 @@ g.test('vector')
break;
}
case 'vec4': {
- const a = vec4(c[0], c[1], c[2], c[3]);
- const b = vec4(c[4], c[5], c[6], c[7]);
+ const a = vec4(scalars[0], scalars[1], scalars[2], scalars[3]);
+ const b = vec4(scalars[4], scalars[5], scalars[6], scalars[7]);
tests = {
- dataType: TypeVec(4, componentType),
- boolType: TypeVec(4, TypeBool),
+ dataType: Type.vec(4, componentType),
+ boolType: Type.vec(4, Type.bool),
cases: [
{ input: [a, b, vec4(F, F, F, F)], expected: vec4(a.x, a.y, a.z, a.w) },
{ input: [a, b, vec4(F, F, F, T)], expected: vec4(a.x, a.y, a.z, b.w) },
@@ -244,7 +259,7 @@ g.test('vector')
await run(
t,
- t.params.component === 'af' ? abstractBuiltin('select') : builtin('select'),
+ dataType[t.params.component].shader_builder,
[tests.dataType, tests.dataType, tests.boolType],
tests.dataType,
t.params,
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.cache.ts
new file mode 100644
index 0000000000..09f4de19ac
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.cache.ts
@@ -0,0 +1,31 @@
+import { abstractInt, i32 } from '../../../../../util/conversion.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { fullI32Range, fullI64Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const fp_cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait === 'abstract' ? 'abstract_float' : trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ 'unfiltered',
+ FP[trait].signInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('sign', {
+ ...fp_cases,
+ i32: () =>
+ fullI32Range().map(i => {
+ const signFunc = (i: number): number => (i < 0 ? -1 : i > 0 ? 1 : 0);
+ return { input: [i32(i)], expected: i32(signFunc(i)) };
+ }),
+ abstract_int: () =>
+ fullI64Range().map(i => {
+ const signFunc = (i: bigint): bigint => (i < 0n ? -1n : i > 0n ? 1n : 0n);
+ return { input: [abstractInt(i)], expected: abstractInt(signFunc(i)) };
+ }),
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts
index a147acf6fb..f7d2e3ccfd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sign.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'sign' builtin function
-S is AbstractFloat, AbstractInt, i32, f32, f16
+S is abstract-float, Type.abstractInt, i32, f32, f16
T is S or vecN<S>
@const fn sign(e: T ) -> T
Returns the sign of e. Component-wise when T is a vector.
@@ -9,48 +9,14 @@ Returns the sign of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import {
- i32,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeAbstractFloat,
-} from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import {
- fullF32Range,
- fullF16Range,
- fullI32Range,
- fullF64Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, abstractIntBuiltin, builtin } from './builtin.js';
+import { d } from './sign.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('sign', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.signInterval);
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.signInterval);
- },
- abstract_float: () => {
- return FP.abstract.generateScalarToIntervalCases(
- fullF64Range(),
- 'unfiltered',
- FP.abstract.signInterval
- );
- },
- i32: () =>
- fullI32Range().map(i => {
- const signFunc = (i: number): number => (i < 0 ? -1 : i > 0 ? 1 : 0);
- return { input: [i32(i)], expected: i32(signFunc(i)) };
- }),
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#sign-builtin')
.desc(`abstract float tests`)
@@ -61,16 +27,28 @@ g.test('abstract_float')
)
.fn(async t => {
const cases = await d.get('abstract_float');
- await run(t, abstractBuiltin('sign'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+ await run(
+ t,
+ abstractFloatBuiltin('sign'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
});
g.test('abstract_int')
.specURL('https://www.w3.org/TR/WGSL/#sign-builtin')
.desc(`abstract int tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_int');
+ await run(t, abstractIntBuiltin('sign'), [Type.abstractInt], Type.abstractInt, t.params, cases);
+ });
g.test('i32')
.specURL('https://www.w3.org/TR/WGSL/#sign-builtin')
@@ -80,7 +58,7 @@ g.test('i32')
)
.fn(async t => {
const cases = await d.get('i32');
- await run(t, builtin('sign'), [TypeI32], TypeI32, t.params, cases);
+ await run(t, builtin('sign'), [Type.i32], Type.i32, t.params, cases);
});
g.test('f32')
@@ -91,7 +69,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('sign'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('sign'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -105,5 +83,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('sign'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('sign'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.cache.ts
new file mode 100644
index 0000000000..74acf6a62c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ [
+ // Well-defined accuracy range
+ ...linearRange(-Math.PI, Math.PI, 100),
+ ...FP[trait].scalarRange(),
+ ],
+ trait === 'abstract' ? 'finite' : 'unfiltered',
+ // sin has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].sinInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('sin', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts
index 4ab3ae7a3d..fc706c5d63 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sin.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'sin' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn sin(e: T ) -> T
Returns the sine of e. Component-wise when T is a vector.
@@ -9,48 +9,33 @@ Returns the sine of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './sin.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('sin', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- // Well-defined accuracy range
- ...linearRange(-Math.PI, Math.PI, 1000),
- ...fullF32Range(),
- ],
- 'unfiltered',
- FP.f32.sinInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- [
- // Well-defined accuracy range
- ...linearRange(-Math.PI, Math.PI, 1000),
- ...fullF16Range(),
- ],
- 'unfiltered',
- FP.f16.sinInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('sin'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -66,7 +51,7 @@ TODO(#792): Decide what the ground-truth is for these tests. [1]
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('sin'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('sin'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -80,5 +65,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('sin'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('sin'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.cache.ts
new file mode 100644
index 0000000000..aba275581d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // sinh has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].sinhInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('sinh', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts
index d9b93a3dc8..5ffe628de3 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sinh.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'sinh' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn sinh(e: T ) -> T
Returns the hyperbolic sine of e. Component-wise when T is a vector.
@@ -9,38 +9,33 @@ Returns the hyperbolic sine of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './sinh.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('sinh', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.sinhInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.sinhInterval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.sinhInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.sinhInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('sinh'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -50,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('sinh'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('sinh'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -64,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('sinh'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('sinh'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.cache.ts
new file mode 100644
index 0000000000..4ba7615b43
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.cache.ts
@@ -0,0 +1,25 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarTripleToIntervalCases(
+ FP[trait].sparseScalarRange(),
+ FP[trait].sparseScalarRange(),
+ FP[trait].sparseScalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // smoothstep has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].smoothStepInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('smoothstep', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts
index 20d2a4edbc..42d8d09ff5 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/smoothstep.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'smoothstep' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn smoothstep(low: T , high: T , x: T ) -> T
Returns the smooth Hermite interpolation between 0 and 1.
@@ -11,62 +11,33 @@ For scalar T, the result is t * t * (3.0 - 2.0 * t), where t = clamp((x - low) /
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { sparseF32Range, sparseF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './smoothstep.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('smoothstep', {
- f32_const: () => {
- return FP.f32.generateScalarTripleToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- sparseF32Range(),
- 'finite',
- FP.f32.smoothStepInterval
- );
- },
- f32_non_const: () => {
- return FP.f32.generateScalarTripleToIntervalCases(
- sparseF32Range(),
- sparseF32Range(),
- sparseF32Range(),
- 'unfiltered',
- FP.f32.smoothStepInterval
- );
- },
- f16_const: () => {
- return FP.f16.generateScalarTripleToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- sparseF16Range(),
- 'finite',
- FP.f16.smoothStepInterval
- );
- },
- f16_non_const: () => {
- return FP.f16.generateScalarTripleToIntervalCases(
- sparseF16Range(),
- sparseF16Range(),
- sparseF16Range(),
- 'unfiltered',
- FP.f16.smoothStepInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('smoothstep'),
+ [Type.abstractFloat, Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -76,7 +47,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('smoothstep'), [TypeF32, TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('smoothstep'), [Type.f32, Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -90,5 +61,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('smoothstep'), [TypeF16, TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('smoothstep'), [Type.f16, Type.f16, Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.cache.ts
new file mode 100644
index 0000000000..2151b2730a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16]_[non_]const
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ nonConst ? 'unfiltered' : 'finite',
+ // sqrt has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].sqrtInterval
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('sqrt', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts
index a092438043..3d6c4390e2 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/sqrt.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'sqrt' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn sqrt(e: T ) -> T
Returns the square root of e. Component-wise when T is a vector.
@@ -9,38 +9,33 @@ Returns the square root of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './sqrt.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('sqrt', {
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'finite', FP.f32.sqrtInterval);
- },
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.sqrtInterval);
- },
- f16_const: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'finite', FP.f16.sqrtInterval);
- },
- f16_non_const: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.sqrtInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract_const');
+ await run(
+ t,
+ abstractFloatBuiltin('sqrt'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -50,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, builtin('sqrt'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('sqrt'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -64,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f16_const' : 'f16_non_const');
- await run(t, builtin('sqrt'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('sqrt'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.cache.ts
new file mode 100644
index 0000000000..28d1e1952b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.cache.ts
@@ -0,0 +1,41 @@
+import { anyOf } from '../../../../../util/compare.js';
+import { FP } from '../../../../../util/floating_point.js';
+import { Case } from '../../case.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// stepInterval's return value can't always be interpreted as a single acceptance
+// interval, valid result may be 0.0 or 1.0 or both of them, but will never be a
+// value in interval (0.0, 1.0).
+// See the comment block on stepInterval for more details
+const makeCase = (trait: 'f32' | 'f16' | 'abstract', edge: number, x: number): Case => {
+ const FPTrait = FP[trait];
+ edge = FPTrait.quantize(edge);
+ x = FPTrait.quantize(x);
+ const expected = FPTrait.stepInterval(edge, x);
+
+ // [0, 0], [1, 1], or [-∞, +∞] cases
+ if (expected.isPoint() || !expected.isFinite()) {
+ return { input: [FPTrait.scalarBuilder(edge), FPTrait.scalarBuilder(x)], expected };
+ }
+
+ // [0, 1] case, valid result is either 0.0 or 1.0.
+ const zeroInterval = FPTrait.toInterval(0);
+ const oneInterval = FPTrait.toInterval(1);
+ return {
+ input: [FPTrait.scalarBuilder(edge), FPTrait.scalarBuilder(x)],
+ expected: anyOf(zeroInterval, oneInterval),
+ };
+};
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait]
+ .sparseScalarRange()
+ .flatMap(edge => FP[trait].sparseScalarRange().map(x => makeCase(trait, edge, x)));
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('step', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts
index 752e2676e6..fd76ee16c1 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/step.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'step' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn step(edge: T ,x: T ) -> T
Returns 1.0 if edge ≤ x, and 0.0 otherwise. Component-wise when T is a vector.
@@ -9,57 +9,33 @@ Returns 1.0 if edge ≤ x, and 0.0 otherwise. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { anyOf } from '../../../../../util/compare.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, Case, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './step.cache.js';
export const g = makeTestGroup(GPUTest);
-// stepInterval's return value can't always be interpreted as a single acceptance
-// interval, valid result may be 0.0 or 1.0 or both of them, but will never be a
-// value in interval (0.0, 1.0).
-// See the comment block on stepInterval for more details
-const makeCase = (trait: 'f32' | 'f16', edge: number, x: number): Case => {
- const FPTrait = FP[trait];
- edge = FPTrait.quantize(edge);
- x = FPTrait.quantize(x);
- const expected = FPTrait.stepInterval(edge, x);
-
- // [0, 0], [1, 1], or [-∞, +∞] cases
- if (expected.isPoint() || !expected.isFinite()) {
- return { input: [FPTrait.scalarBuilder(edge), FPTrait.scalarBuilder(x)], expected };
- }
-
- // [0, 1] case, valid result is either 0.0 or 1.0.
- const zeroInterval = FPTrait.toInterval(0);
- const oneInterval = FPTrait.toInterval(1);
- return {
- input: [FPTrait.scalarBuilder(edge), FPTrait.scalarBuilder(x)],
- expected: anyOf(zeroInterval, oneInterval),
- };
-};
-
-export const d = makeCaseCache('step', {
- f32: () => {
- return fullF32Range().flatMap(edge => fullF32Range().map(x => makeCase('f32', edge, x)));
- },
- f16: () => {
- return fullF16Range().flatMap(edge => fullF16Range().map(x => makeCase('f16', edge, x)));
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('step'),
+ [Type.abstractFloat, Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -69,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('step'), [TypeF32, TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('step'), [Type.f32, Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -83,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('step'), [TypeF16, TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('step'), [Type.f16, Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.cache.ts
new file mode 100644
index 0000000000..8d8e0d980b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.cache.ts
@@ -0,0 +1,23 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { linearRange } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ [
+ // Well-defined accuracy range
+ ...linearRange(-Math.PI, Math.PI, 100),
+ ...FP[trait].scalarRange(),
+ ],
+ 'unfiltered',
+ // tan has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].tanInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('tan', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts
index be3bdee046..7b682d0968 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tan.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'tan' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn tan(e: T ) -> T
Returns the tangent of e. Component-wise when T is a vector.
@@ -9,48 +9,33 @@ Returns the tangent of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range, linearRange } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './tan.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('tan', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(
- [
- // Defined accuracy range
- ...linearRange(-Math.PI, Math.PI, 100),
- ...fullF32Range(),
- ],
- 'unfiltered',
- FP.f32.tanInterval
- );
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(
- [
- // Defined accuracy range
- ...linearRange(-Math.PI, Math.PI, 100),
- ...fullF16Range(),
- ],
- 'unfiltered',
- FP.f16.tanInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('tan'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -60,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('tan'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('tan'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -74,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('tan'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('tan'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.cache.ts
new file mode 100644
index 0000000000..5bada7fef5
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.cache.ts
@@ -0,0 +1,18 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ 'unfiltered',
+ // tanh has an inherited accuracy, so is only expected to be as accurate as f32
+ FP[trait !== 'abstract' ? trait : 'f32'].tanhInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('tanh', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts
index 3aca5b924b..17978926a3 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/tanh.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'tanh' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn tanh(e: T ) -> T
Returns the hyperbolic tangent of e. Component-wise when T is a vector.
@@ -9,32 +9,33 @@ Returns the hyperbolic tangent of e. Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeF16 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF16Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
-import { allInputSources, run } from '../../expression.js';
+import { Type } from '../../../../../util/conversion.js';
+import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './tanh.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('tanh', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.tanhInterval);
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(fullF16Range(), 'unfiltered', FP.f16.tanhInterval);
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
.params(u =>
- u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
)
- .unimplemented();
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(
+ t,
+ abstractFloatBuiltin('tanh'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
+ });
g.test('f32')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
@@ -44,7 +45,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('tanh'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('tanh'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -58,5 +59,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('tanh'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('tanh'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.ts
deleted file mode 100644
index 0ecb9964cf..0000000000
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.ts
+++ /dev/null
@@ -1,160 +0,0 @@
-export const description = `
-Execution tests for the 'textureDimension' builtin function
-
-The dimensions of the texture in texels.
-For textures based on cubes, the results are the dimensions of each face of the cube.
-Cube faces are square, so the x and y components of the result are equal.
-If level is outside the range [0, textureNumLevels(t)) then any valid value for the return type may be returned.
-`;
-
-import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
-import { GPUTest } from '../../../../../gpu_test.js';
-
-export const g = makeTestGroup(GPUTest);
-
-g.test('sampled')
- .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
- .desc(
- `
-T: f32, i32, u32
-
-fn textureDimensions(t: texture_1d<T>) -> u32
-fn textureDimensions(t: texture_1d<T>, level: u32) -> u32
-fn textureDimensions(t: texture_2d<T>) -> vec2<u32>
-fn textureDimensions(t: texture_2d<T>, level: u32) -> vec2<u32>
-fn textureDimensions(t: texture_2d_array<T>) -> vec2<u32>
-fn textureDimensions(t: texture_2d_array<T>, level: u32) -> vec2<u32>
-fn textureDimensions(t: texture_3d<T>) -> vec3<u32>
-fn textureDimensions(t: texture_3d<T>, level: u32) -> vec3<u32>
-fn textureDimensions(t: texture_cube<T>) -> vec2<u32>
-fn textureDimensions(t: texture_cube<T>, level: u32) -> vec2<u32>
-fn textureDimensions(t: texture_cube_array<T>) -> vec2<u32>
-fn textureDimensions(t: texture_cube_array<T>, level: u32) -> vec2<u32>
-fn textureDimensions(t: texture_multisampled_2d<T>)-> vec2<u32>
-
-Parameters:
- * t: the sampled texture
- * level:
- - The mip level, with level 0 containing a full size version of the texture.
- - If omitted, the dimensions of level 0 are returned.
-`
- )
- .params(u =>
- u
- .combine('texture_type', [
- 'texture_1d',
- 'texture_2d',
- 'texture_2d_array',
- 'texture_3d',
- 'texture_cube',
- 'texture_cube_array',
- 'texture_multisampled_2d',
- ] as const)
- .beginSubcases()
- .combine('sampled_type', ['f32-only', 'i32', 'u32'] as const)
- .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const)
- )
- .unimplemented();
-
-g.test('depth')
- .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
- .desc(
- `
-fn textureDimensions(t: texture_depth_2d) -> vec2<u32>
-fn textureDimensions(t: texture_depth_2d, level: u32) -> vec2<u32>
-fn textureDimensions(t: texture_depth_2d_array) -> vec2<u32>
-fn textureDimensions(t: texture_depth_2d_array, level: u32) -> vec2<u32>
-fn textureDimensions(t: texture_depth_cube) -> vec2<u32>
-fn textureDimensions(t: texture_depth_cube, level: u32) -> vec2<u32>
-fn textureDimensions(t: texture_depth_cube_array) -> vec2<u32>
-fn textureDimensions(t: texture_depth_cube_array, level: u32) -> vec2<u32>
-fn textureDimensions(t: texture_depth_multisampled_2d)-> vec2<u32>
-
-Parameters:
- * t: the depth or multisampled texture
- * level:
- - The mip level, with level 0 containing a full size version of the texture.
- - If omitted, the dimensions of level 0 are returned.
-`
- )
- .params(u =>
- u
- .combine('texture_type', [
- 'texture_depth_2d',
- 'texture_depth_2d_array',
- 'texture_depth_cube',
- 'texture_depth_cube_array',
- 'texture_depth_multisampled_2d',
- ])
- .beginSubcases()
- .combine('level', [undefined, 0, 1, 'textureNumLevels', 'textureNumLevels+1'] as const)
- )
- .unimplemented();
-
-g.test('storage')
- .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
- .desc(
- `
-F: rgba8unorm
- rgba8snorm
- rgba8uint
- rgba8sint
- rgba16uint
- rgba16sint
- rgba16float
- r32uint
- r32sint
- r32float
- rg32uint
- rg32sint
- rg32float
- rgba32uint
- rgba32sint
- rgba32float
-A: read, write, read_write
-
-fn textureDimensions(t: texture_storage_1d<F,A>) -> u32
-fn textureDimensions(t: texture_storage_2d<F,A>) -> vec2<u32>
-fn textureDimensions(t: texture_storage_2d_array<F,A>) -> vec2<u32>
-fn textureDimensions(t: texture_storage_3d<F,A>) -> vec3<u32>
-
-Parameters:
- * t: the storage texture
-`
- )
- .params(u =>
- u
- .combine('texel_format', [
- 'rgba8unorm',
- 'rgba8snorm',
- 'rgba8uint',
- 'rgba8sint',
- 'rgba16uint',
- 'rgba16sint',
- 'rgba16float',
- 'r32uint',
- 'r32sint',
- 'r32float',
- 'rg32uint',
- 'rg32sint',
- 'rg32float',
- 'rgba32uint',
- 'rgba32sint',
- 'rgba32float',
- ] as const)
- .beginSubcases()
- .combine('access_mode', ['read', 'write', 'read_write'] as const)
- )
- .unimplemented();
-
-g.test('external')
- .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
- .desc(
- `
-fn textureDimensions(t: texture_external) -> vec2<u32>
-
-Parameters:
- * t: the external texture
-`
- )
- .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimensions.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimensions.spec.ts
new file mode 100644
index 0000000000..1e025ca618
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureDimensions.spec.ts
@@ -0,0 +1,518 @@
+export const description = `
+Execution tests for the 'textureDimensions' builtin function
+
+The dimensions of the texture in texels.
+For textures based on cubes, the results are the dimensions of each face of the cube.
+Cube faces are square, so the x and y components of the result are equal.
+If level is outside the range [0, textureNumLevels(t)) then any valid value for the return type may be returned.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import {
+ kAllTextureFormats,
+ kColorTextureFormats,
+ kTextureFormatInfo,
+ sampleTypeForFormatAndAspect,
+ textureDimensionAndFormatCompatible,
+} from '../../../../../format_info.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { align } from '../../../../../util/math.js';
+
+export const g = makeTestGroup(GPUTest);
+
+/// The maximum number of texture mipmap levels to test.
+/// Keep this small to reduce memory and test permutations.
+const kMaxMipsForTest = 3;
+
+/// The maximum number of texture samples to test.
+const kMaxSamplesForTest = 4;
+
+/// All the possible GPUTextureViewDimensions.
+const kAllViewDimensions: readonly GPUTextureViewDimension[] = [
+ '1d',
+ '2d',
+ '2d-array',
+ '3d',
+ 'cube',
+ 'cube-array',
+] as const;
+
+/** @returns the aspects to test for the given format */
+function aspectsForFormat(format: GPUTextureFormat): readonly GPUTextureAspect[] {
+ const formatInfo = kTextureFormatInfo[format];
+ if (formatInfo.depth !== undefined && formatInfo.stencil !== undefined) {
+ return ['depth-only', 'stencil-only'];
+ }
+ return ['all'];
+}
+
+/** @returns the sample counts to test for the given format */
+function samplesForFormat(format: GPUTextureFormat): readonly number[] {
+ const info = kTextureFormatInfo[format];
+ return info.multisample ? [1, kMaxSamplesForTest] : [1];
+}
+
+/**
+ * @returns a list of number of texture mipmap levels to test, given the format, view dimensions and
+ * number of samples.
+ */
+function textureMipCount(params: {
+ format: GPUTextureFormat;
+ dimensions: GPUTextureViewDimension;
+ samples?: number;
+}): readonly number[] {
+ if (params.samples !== undefined && params.samples !== 1) {
+ // https://www.w3.org/TR/webgpu/#texture-creation
+ // If descriptor.sampleCount > 1: descriptor.mipLevelCount must be 1.
+ return [1];
+ }
+ if (textureDimensionsForViewDimensions(params.dimensions) === '1d') {
+ // https://www.w3.org/TR/webgpu/#dom-gputexturedimension-2d
+ // Only "2d" textures may have mipmaps, be multisampled, use a compressed or depth/stencil
+ // format, and be used as a render attachment.
+ return [1];
+ }
+ return [1, kMaxMipsForTest];
+}
+
+/**
+ * @returns a list of GPUTextureViewDescriptor.baseMipLevel to test, give the texture mipmap count.
+ */
+function baseMipLevel(params: { textureMipCount: number }): readonly number[] {
+ const out: number[] = [];
+ for (let i = 0; i < params.textureMipCount; i++) {
+ out.push(i);
+ }
+ return out;
+}
+
+/**
+ * @returns the argument values for the textureDimensions() `level` parameter to test.
+ * An `undefined` represents a call to textureDimensions() without the level argument.
+ */
+function textureDimensionsLevel(params: {
+ samples?: number;
+ textureMipCount: number;
+ baseMipLevel: number;
+}): readonly (number | undefined)[] {
+ if (params.samples !== undefined && params.samples > 1) {
+ return [undefined]; // textureDimensions() overload with `level` not available.
+ }
+ const out: (number | undefined)[] = [undefined];
+ for (let i = 0; i < params.textureMipCount - params.baseMipLevel; i++) {
+ out.push(i);
+ }
+ return out;
+}
+
+/** @returns the GPUTextureViewDimensions to test for the format and number of samples */
+function viewDimensions(params: {
+ format: GPUTextureFormat;
+ samples?: number;
+}): readonly GPUTextureViewDimension[] {
+ if (params.samples !== undefined && params.samples > 1) {
+ // https://www.w3.org/TR/webgpu/#dom-gputexturedimension-2d
+ // Only 2d textures can be multisampled
+ return ['2d'];
+ }
+
+ return kAllViewDimensions.filter(dim =>
+ textureDimensionAndFormatCompatible(textureDimensionsForViewDimensions(dim), params.format)
+ );
+}
+
+/** @returns the GPUTextureDimension for the GPUTextureViewDimension */
+function textureDimensionsForViewDimensions(dim: GPUTextureViewDimension): GPUTextureDimension {
+ switch (dim) {
+ case '1d':
+ return '1d';
+ case '2d':
+ case '2d-array':
+ case 'cube':
+ case 'cube-array':
+ return '2d';
+ case '3d':
+ return '3d';
+ }
+}
+
+/** TestValues holds the texture size and expected return value of textureDimensions() */
+type TestValues = {
+ /** The value to pass to GPUTextureDescriptor.size, when creating the texture */
+ size: number[];
+ /** The expected result of calling textureDimensions() */
+ expected: number[];
+};
+
+/** @returns The TestValues to use for the given texture dimensions and format */
+function testValues(params: {
+ dimensions: GPUTextureViewDimension;
+ format: GPUTextureFormat;
+ baseMipLevel: number;
+ textureDimensionsLevel?: number;
+}): TestValues {
+ // The minimum dimension length, given the number of mipmap levels that are being tested.
+ const kMinLen = 1 << kMaxMipsForTest;
+ const kNumCubeFaces = 6;
+
+ const formatInfo = kTextureFormatInfo[params.format];
+ const bw = formatInfo.blockWidth;
+ const bh = formatInfo.blockHeight;
+ let mip = params.baseMipLevel;
+ if (params.textureDimensionsLevel !== undefined) {
+ mip += params.textureDimensionsLevel;
+ }
+
+ // Magic constants to multiply the minimum texture dimensions with, to provide
+ // different dimension values in the test. These could be parameterized, but
+ // these are currently fixed to reduce the number of test parameterizations.
+ const kMultipleA = 2;
+ const kMultipleB = 3;
+ const kMultipleC = 4;
+
+ switch (params.dimensions) {
+ case '1d': {
+ const w = align(kMinLen, bw) * kMultipleA;
+ return { size: [w], expected: [w >>> mip] };
+ }
+ case '2d': {
+ const w = align(kMinLen, bw) * kMultipleA;
+ const h = align(kMinLen, bh) * kMultipleB;
+ return { size: [w, h], expected: [w >>> mip, h >>> mip] };
+ }
+ case '2d-array': {
+ const w = align(kMinLen, bw) * kMultipleC;
+ const h = align(kMinLen, bh) * kMultipleB;
+ return { size: [w, h, 4], expected: [w >>> mip, h >>> mip] };
+ }
+ case '3d': {
+ const w = align(kMinLen, bw) * kMultipleA;
+ const h = align(kMinLen, bh) * kMultipleB;
+ const d = kMinLen * kMultipleC;
+ return {
+ size: [w, h, d],
+ expected: [w >>> mip, h >>> mip, d >>> mip],
+ };
+ }
+ case 'cube': {
+ const l = align(kMinLen, bw) * align(kMinLen, bh) * kMultipleB;
+ return {
+ size: [l, l, kNumCubeFaces],
+ expected: [l >>> mip, l >>> mip],
+ };
+ }
+ case 'cube-array': {
+ const l = align(kMinLen, bw) * align(kMinLen, bh) * kMultipleC;
+ return {
+ size: [l, l, kNumCubeFaces * 3],
+ expected: [l >>> mip, l >>> mip],
+ };
+ }
+ }
+}
+
+/**
+ * Builds a shader module with the texture view bound to the WGSL texture with the given WGSL type,
+ * which calls textureDimensions(), assigning the result to an output buffer.
+ * This shader is executed with a compute shader, and the output buffer is compared to
+ * `values.expected`.
+ */
+function run(
+ t: GPUTest,
+ view: GPUTextureView,
+ textureType: string,
+ levelArg: number | undefined,
+ values: TestValues
+) {
+ const outputType = values.expected.length > 1 ? `vec${values.expected.length}u` : 'u32';
+ const wgsl = `
+@group(0) @binding(0) var texture : ${textureType};
+@group(0) @binding(1) var<storage, read_write> output : ${outputType};
+
+@compute @workgroup_size(1)
+fn main() {
+output = ${
+ levelArg !== undefined
+ ? `textureDimensions(texture, ${levelArg})`
+ : 'textureDimensions(texture)'
+ };
+}
+`;
+ const module = t.device.createShaderModule({
+ code: wgsl,
+ });
+ const pipeline = t.device.createComputePipeline({
+ compute: { module },
+ layout: 'auto',
+ });
+ const outputBuffer = t.device.createBuffer({
+ size: 32,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE,
+ });
+ const bindgroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: view },
+ { binding: 1, resource: { buffer: outputBuffer } },
+ ],
+ });
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindgroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.device.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(outputBuffer, new Uint32Array(values.expected));
+}
+
+/** @returns true if the GPUTextureViewDimension is valid for a storage texture */
+function dimensionsValidForStorage(dimensions: GPUTextureViewDimension) {
+ switch (dimensions) {
+ case '1d':
+ case '2d':
+ case '2d-array':
+ case '3d':
+ return true;
+ default:
+ return false;
+ }
+}
+
+g.test('sampled_and_multisampled')
+ .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
+ .desc(
+ `
+T: f32, i32, u32
+
+fn textureDimensions(t: texture_1d<T>) -> u32
+fn textureDimensions(t: texture_1d<T>, level: u32) -> u32
+fn textureDimensions(t: texture_2d<T>) -> vec2<u32>
+fn textureDimensions(t: texture_2d<T>, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_2d_array<T>) -> vec2<u32>
+fn textureDimensions(t: texture_2d_array<T>, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_3d<T>) -> vec3<u32>
+fn textureDimensions(t: texture_3d<T>, level: u32) -> vec3<u32>
+fn textureDimensions(t: texture_cube<T>) -> vec2<u32>
+fn textureDimensions(t: texture_cube<T>, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_cube_array<T>) -> vec2<u32>
+fn textureDimensions(t: texture_cube_array<T>, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_multisampled_2d<T>)-> vec2<u32>
+
+Parameters:
+ * t: the sampled texture
+ * level:
+ - The mip level, with level 0 containing a full size version of the texture.
+ - If omitted, the dimensions of level 0 are returned.
+`
+ )
+ .params(u =>
+ u
+ .combine('format', kAllTextureFormats)
+ .unless(p => kTextureFormatInfo[p.format].color?.type === 'unfilterable-float')
+ .expand('aspect', u => aspectsForFormat(u.format))
+ .expand('samples', u => samplesForFormat(u.format))
+ .beginSubcases()
+ .expand('dimensions', viewDimensions)
+ .expand('textureMipCount', textureMipCount)
+ .expand('baseMipLevel', baseMipLevel)
+ .expand('textureDimensionsLevel', textureDimensionsLevel)
+ )
+ .beforeAllSubcases(t => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+ })
+ .fn(t => {
+ t.skipIfTextureViewDimensionNotSupported(t.params.dimensions);
+ const values = testValues(t.params);
+ const texture = t.device.createTexture({
+ size: values.size,
+ dimension: textureDimensionsForViewDimensions(t.params.dimensions),
+ ...(t.isCompatibility && { textureBindingViewDimension: t.params.dimensions }),
+ usage:
+ t.params.samples === 1
+ ? GPUTextureUsage.TEXTURE_BINDING
+ : GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
+ format: t.params.format,
+ sampleCount: t.params.samples,
+ mipLevelCount: t.params.textureMipCount,
+ });
+ const textureView = texture.createView({
+ dimension: t.params.dimensions,
+ aspect: t.params.aspect,
+ baseMipLevel: t.params.baseMipLevel,
+ });
+
+ function wgslSampledTextureType(): string {
+ const base = t.params.samples !== 1 ? 'texture_multisampled' : 'texture';
+ const dimensions = t.params.dimensions.replace('-', '_');
+ const sampleType = sampleTypeForFormatAndAspect(t.params.format, t.params.aspect);
+ switch (sampleType) {
+ case 'depth':
+ case 'float':
+ return `${base}_${dimensions}<f32>`;
+ case 'uint':
+ return `${base}_${dimensions}<u32>`;
+ case 'sint':
+ return `${base}_${dimensions}<i32>`;
+ case 'unfilterable-float':
+ throw new Error(`'${t.params.format}' does not support sampling`);
+ }
+ }
+
+ run(t, textureView, wgslSampledTextureType(), t.params.textureDimensionsLevel, values);
+ });
+
+g.test('depth')
+ .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
+ .desc(
+ `
+fn textureDimensions(t: texture_depth_2d) -> vec2<u32>
+fn textureDimensions(t: texture_depth_2d, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_depth_2d_array) -> vec2<u32>
+fn textureDimensions(t: texture_depth_2d_array, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_depth_cube) -> vec2<u32>
+fn textureDimensions(t: texture_depth_cube, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_depth_cube_array) -> vec2<u32>
+fn textureDimensions(t: texture_depth_cube_array, level: u32) -> vec2<u32>
+fn textureDimensions(t: texture_depth_multisampled_2d)-> vec2<u32>
+
+Parameters:
+ * t: the depth or multisampled texture
+ * level:
+ - The mip level, with level 0 containing a full size version of the texture.
+ - If omitted, the dimensions of level 0 are returned.
+`
+ )
+ .params(u =>
+ u
+ .combine('format', kAllTextureFormats)
+ .filter(p => !!kTextureFormatInfo[p.format].depth)
+ .expand('aspect', u => aspectsForFormat(u.format))
+ .unless(u => u.aspect === 'stencil-only')
+ .expand('samples', u => samplesForFormat(u.format))
+ .beginSubcases()
+ .expand('dimensions', viewDimensions)
+ .expand('textureMipCount', textureMipCount)
+ .expand('baseMipLevel', baseMipLevel)
+ .expand('textureDimensionsLevel', textureDimensionsLevel)
+ )
+ .beforeAllSubcases(t => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+ })
+ .fn(t => {
+ t.skipIfTextureViewDimensionNotSupported(t.params.dimensions);
+ const values = testValues(t.params);
+ const texture = t.device.createTexture({
+ size: values.size,
+ dimension: textureDimensionsForViewDimensions(t.params.dimensions),
+ ...(t.isCompatibility && { textureBindingViewDimension: t.params.dimensions }),
+ usage:
+ t.params.samples === 1
+ ? GPUTextureUsage.TEXTURE_BINDING
+ : GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
+ format: t.params.format,
+ sampleCount: t.params.samples,
+ mipLevelCount: t.params.textureMipCount,
+ });
+ const textureView = texture.createView({
+ dimension: t.params.dimensions,
+ aspect: t.params.aspect,
+ baseMipLevel: t.params.baseMipLevel,
+ });
+
+ function wgslDepthTextureType(): string {
+ const base = t.params.samples !== 1 ? 'texture_depth_multisampled' : 'texture_depth';
+ const dimensions = t.params.dimensions.replace('-', '_');
+ return `${base}_${dimensions}`;
+ }
+
+ run(t, textureView, wgslDepthTextureType(), t.params.textureDimensionsLevel, values);
+ });
+
+g.test('storage')
+ .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
+ .desc(
+ `
+F: rgba8unorm
+ rgba8snorm
+ rgba8uint
+ rgba8sint
+ rgba16uint
+ rgba16sint
+ rgba16float
+ r32uint
+ r32sint
+ r32float
+ rg32uint
+ rg32sint
+ rg32float
+ rgba32uint
+ rgba32sint
+ rgba32float
+A: read, write, read_write
+
+fn textureDimensions(t: texture_storage_1d<F,A>) -> u32
+fn textureDimensions(t: texture_storage_2d<F,A>) -> vec2<u32>
+fn textureDimensions(t: texture_storage_2d_array<F,A>) -> vec2<u32>
+fn textureDimensions(t: texture_storage_3d<F,A>) -> vec3<u32>
+
+Parameters:
+ * t: the storage texture
+`
+ )
+ .params(u =>
+ u
+ .combine('format', kColorTextureFormats)
+ .filter(p => kTextureFormatInfo[p.format].color?.storage === true)
+ .expand('aspect', u => aspectsForFormat(u.format))
+ .beginSubcases()
+ .expand('dimensions', u => viewDimensions(u).filter(dimensionsValidForStorage))
+ .expand('textureMipCount', textureMipCount)
+ .expand('baseMipLevel', baseMipLevel)
+ )
+ .beforeAllSubcases(t => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ t.skipIfTextureFormatNotUsableAsStorageTexture(t.params.format);
+ t.selectDeviceOrSkipTestCase(info.feature);
+ })
+ .fn(t => {
+ const values = testValues(t.params);
+ const texture = t.device.createTexture({
+ size: values.size,
+ dimension: textureDimensionsForViewDimensions(t.params.dimensions),
+ usage: GPUTextureUsage.STORAGE_BINDING,
+ format: t.params.format,
+ mipLevelCount: t.params.textureMipCount,
+ });
+ const textureView = texture.createView({
+ dimension: t.params.dimensions,
+ aspect: t.params.aspect,
+ mipLevelCount: 1,
+ baseMipLevel: t.params.baseMipLevel,
+ });
+
+ function wgslStorageTextureType(): string {
+ const dimensions = t.params.dimensions.replace('-', '_');
+ return `texture_storage_${dimensions}<${t.params.format}, write>`;
+ }
+
+ run(t, textureView, wgslStorageTextureType(), undefined, values);
+ });
+
+g.test('external')
+ .specURL('https://www.w3.org/TR/WGSL/#texturedimensions')
+ .desc(
+ `
+fn textureDimensions(t: texture_external) -> vec2<u32>
+
+Parameters:
+ * t: the external texture
+`
+ )
+ .unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts
index f5b01dfc63..7c743576e9 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSample.spec.ts
@@ -1,26 +1,24 @@
export const description = `
Samples a texture.
-
-Must only be used in a fragment shader stage.
-Must only be invoked in uniform control flow.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
-import { GPUTest } from '../../../../../gpu_test.js';
+import { kEncodableTextureFormats, kTextureFormatInfo } from '../../../../../format_info.js';
+import { GPUTest, TextureTestMixin } from '../../../../../gpu_test.js';
+import { hashU32 } from '../../../../../util/math.js';
+import { kTexelRepresentationInfo } from '../../../../../util/texture/texel_data.js';
+import {
+ vec2,
+ createRandomTexelView,
+ TextureCall,
+ putDataInTextureThenDrawAndCheckResults,
+ generateSamplePoints,
+ kSamplePointMethods,
+} from './texture_utils.js';
import { generateCoordBoundaries, generateOffsets } from './utils.js';
-export const g = makeTestGroup(GPUTest);
-
-g.test('stage')
- .specURL('https://www.w3.org/TR/WGSL/#texturesample')
- .desc(
- `
-Tests that 'textureSample' can only be called in 'fragment' shaders.
-`
- )
- .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const))
- .unimplemented();
+export const g = makeTestGroup(TextureTestMixin(GPUTest));
g.test('control_flow')
.specURL('https://www.w3.org/TR/WGSL/#texturesample')
@@ -70,11 +68,74 @@ Parameters:
Values outside of this range will result in a shader-creation error.
`
)
- .paramsSubcasesOnly(u =>
+ .params(u =>
u
- .combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
- .combine('coords', generateCoordBoundaries(2))
- .combine('offset', generateOffsets(2))
+ .combine('format', kEncodableTextureFormats)
+ .filter(t => {
+ const type = kTextureFormatInfo[t.format].color?.type;
+ return type === 'float' || type === 'unfilterable-float';
+ })
+ .combine('sample_points', kSamplePointMethods)
+ .combine('addressModeU', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
+ .combine('addressModeV', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
+ .combine('minFilter', ['nearest', 'linear'] as const)
+ .combine('offset', [false, true] as const)
+ )
+ .beforeAllSubcases(t => {
+ const format = kTexelRepresentationInfo[t.params.format];
+ t.skipIfTextureFormatNotSupported(t.params.format);
+ const hasFloat32 = format.componentOrder.some(c => {
+ const info = format.componentInfo[c]!;
+ return info.dataType === 'float' && info.bitLength === 32;
+ });
+ if (hasFloat32) {
+ t.selectDeviceOrSkipTestCase('float32-filterable');
+ }
+ })
+ .fn(async t => {
+ const descriptor: GPUTextureDescriptor = {
+ format: t.params.format,
+ size: { width: 8, height: 8 },
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING,
+ };
+ const texelView = createRandomTexelView(descriptor);
+ const calls: TextureCall<vec2>[] = generateSamplePoints(50, t.params.minFilter === 'nearest', {
+ method: t.params.sample_points,
+ textureWidth: 8,
+ textureHeight: 8,
+ }).map((c, i) => {
+ const hash = hashU32(i) & 0xff;
+ return {
+ builtin: 'textureSample',
+ coordType: 'f',
+ coords: c,
+ offset: t.params.offset ? [(hash & 15) - 8, (hash >> 4) - 8] : undefined,
+ };
+ });
+ const sampler: GPUSamplerDescriptor = {
+ addressModeU: t.params.addressModeU,
+ addressModeV: t.params.addressModeV,
+ minFilter: t.params.minFilter,
+ magFilter: t.params.minFilter,
+ };
+ const res = await putDataInTextureThenDrawAndCheckResults(
+ t.device,
+ { texels: texelView, descriptor },
+ sampler,
+ calls
+ );
+ t.expectOK(res);
+ });
+
+g.test('sampled_2d_coords,derivatives')
+ .specURL('https://www.w3.org/TR/WGSL/#texturesample')
+ .desc(
+ `
+fn textureSample(t: texture_2d<f32>, s: sampler, coords: vec2<f32>) -> vec4<f32>
+fn textureSample(t: texture_2d<f32>, s: sampler, coords: vec2<f32>, offset: vec2<i32>) -> vec4<f32>
+
+test mip level selection based on derivatives
+ `
)
.unimplemented();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts
index 786bce4830..1c61c1a5f2 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts
@@ -2,8 +2,6 @@ export const description = `
Execution tests for the 'textureSampleBias' builtin function
Samples a texture with a bias to the mip level.
-Must only be used in a fragment shader stage.
-Must only be invoked in uniform control flow.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
@@ -13,26 +11,6 @@ import { generateCoordBoundaries, generateOffsets } from './utils.js';
export const g = makeTestGroup(GPUTest);
-g.test('stage')
- .specURL('https://www.w3.org/TR/WGSL/#texturesamplebias')
- .desc(
- `
-Tests that 'textureSampleBias' can only be called in 'fragment' shaders.
-`
- )
- .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const))
- .unimplemented();
-
-g.test('control_flow')
- .specURL('https://www.w3.org/TR/WGSL/#texturesamplebias')
- .desc(
- `
-Tests that 'textureSampleBias' can only be called in uniform control flow.
-`
- )
- .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const))
- .unimplemented();
-
g.test('sampled_2d_coords')
.specURL('https://www.w3.org/TR/WGSL/#texturesamplebias')
.desc(
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts
index 9f723fac2e..eae5098257 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/textureSampleCompare.spec.ts
@@ -1,8 +1,5 @@
export const description = `
Samples a depth texture and compares the sampled depth values against a reference value.
-
-Must only be used in a fragment shader stage.
-Must only be invoked in uniform control flow.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
@@ -12,26 +9,6 @@ import { generateCoordBoundaries, generateOffsets } from './utils.js';
export const g = makeTestGroup(GPUTest);
-g.test('stage')
- .specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare')
- .desc(
- `
-Tests that 'textureSampleCompare' can only be called in 'fragment' shaders.
-`
- )
- .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const))
- .unimplemented();
-
-g.test('control_flow')
- .specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare')
- .desc(
- `
-Tests that 'textureSampleCompare' can only be called in uniform control flow.
-`
- )
- .params(u => u.combine('stage', ['fragment', 'vertex', 'compute'] as const))
- .unimplemented();
-
g.test('2d_coords')
.specURL('https://www.w3.org/TR/WGSL/#texturesamplecompare')
.desc(
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/texture_utils.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/texture_utils.ts
new file mode 100644
index 0000000000..fe2da53d5a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/texture_utils.ts
@@ -0,0 +1,809 @@
+import { assert, range, unreachable } from '../../../../../../common/util/util.js';
+import { EncodableTextureFormat } from '../../../../../format_info.js';
+import { float32ToUint32 } from '../../../../../util/conversion.js';
+import { align, clamp, hashU32, lerp, quantizeToF32 } from '../../../../../util/math.js';
+import {
+ kTexelRepresentationInfo,
+ PerTexelComponent,
+ TexelRepresentationInfo,
+} from '../../../../../util/texture/texel_data.js';
+import { TexelView } from '../../../../../util/texture/texel_view.js';
+import { createTextureFromTexelView } from '../../../../../util/texture.js';
+import { reifyExtent3D } from '../../../../../util/unions.js';
+
+function getLimitValue(v: number) {
+ switch (v) {
+ case Number.POSITIVE_INFINITY:
+ return 1000;
+ case Number.NEGATIVE_INFINITY:
+ return -1000;
+ default:
+ return v;
+ }
+}
+
+function getValueBetweenMinAndMaxTexelValueInclusive(
+ rep: TexelRepresentationInfo,
+ normalized: number
+) {
+ return lerp(
+ getLimitValue(rep.numericRange!.min),
+ getLimitValue(rep.numericRange!.max),
+ normalized
+ );
+}
+
+/**
+ * Creates a TexelView filled with random values.
+ */
+export function createRandomTexelView(info: {
+ format: GPUTextureFormat;
+ size: GPUExtent3D;
+}): TexelView {
+ const rep = kTexelRepresentationInfo[info.format as EncodableTextureFormat];
+ const generator = (coords: Required<GPUOrigin3DDict>): Readonly<PerTexelComponent<number>> => {
+ const texel: PerTexelComponent<number> = {};
+ for (const component of rep.componentOrder) {
+ const rnd = hashU32(coords.x, coords.y, coords.z, component.charCodeAt(0));
+ const normalized = clamp(rnd / 0xffffffff, { min: 0, max: 1 });
+ texel[component] = getValueBetweenMinAndMaxTexelValueInclusive(rep, normalized);
+ }
+ return quantize(texel, rep);
+ };
+ return TexelView.fromTexelsAsColors(info.format as EncodableTextureFormat, generator);
+}
+
+export type vec2 = [number, number];
+export type vec3 = [number, number, number];
+export type vec4 = [number, number, number, number];
+export type Dimensionality = number | vec2 | vec3;
+
+type TextureCallArgKeys = keyof TextureCallArgs<number>;
+const kTextureCallArgNames: TextureCallArgKeys[] = [
+ 'coords',
+ 'mipLevel',
+ 'arrayIndex',
+ 'ddx',
+ 'ddy',
+ 'offset',
+];
+
+export interface TextureCallArgs<T extends Dimensionality> {
+ coords?: T;
+ mipLevel?: number;
+ arrayIndex?: number;
+ ddx?: T;
+ ddy?: T;
+ offset?: T;
+}
+
+export interface TextureCall<T extends Dimensionality> extends TextureCallArgs<T> {
+ builtin: 'textureSample' | 'textureLoad';
+ coordType: 'f';
+}
+
+function toArray(coords: Dimensionality): number[] {
+ if (coords instanceof Array) {
+ return coords;
+ }
+ return [coords];
+}
+
+function quantize(texel: PerTexelComponent<number>, repl: TexelRepresentationInfo) {
+ return repl.bitsToNumber(repl.unpackBits(new Uint8Array(repl.pack(repl.encode(texel)))));
+}
+
+function apply(a: number[], b: number[], op: (x: number, y: number) => number) {
+ assert(a.length === b.length, `apply(${a}, ${b}): arrays must have same length`);
+ return a.map((v, i) => op(v, b[i]));
+}
+
+const add = (a: number[], b: number[]) => apply(a, b, (x, y) => x + y);
+
+export interface Texture {
+ texels: TexelView;
+ descriptor: GPUTextureDescriptor;
+}
+
+/**
+ * Returns the expect value for a WGSL builtin texture function
+ */
+export function expected<T extends Dimensionality>(
+ call: TextureCall<T>,
+ texture: Texture,
+ sampler: GPUSamplerDescriptor
+): PerTexelComponent<number> {
+ const rep = kTexelRepresentationInfo[texture.texels.format];
+ const textureExtent = reifyExtent3D(texture.descriptor.size);
+ const textureSize = [textureExtent.width, textureExtent.height, textureExtent.depthOrArrayLayers];
+ const addressMode = [
+ sampler.addressModeU ?? 'clamp-to-edge',
+ sampler.addressModeV ?? 'clamp-to-edge',
+ sampler.addressModeW ?? 'clamp-to-edge',
+ ];
+
+ const load = (at: number[]) =>
+ texture.texels.color({
+ x: Math.floor(at[0]),
+ y: Math.floor(at[1] ?? 0),
+ z: Math.floor(at[2] ?? 0),
+ });
+
+ switch (call.builtin) {
+ case 'textureSample': {
+ const coords = toArray(call.coords!);
+
+ // convert normalized to absolute texel coordinate
+ // ┌───┬───┬───┬───┐
+ // │ a │ │ │ │ norm: a = 1/8, b = 7/8
+ // ├───┼───┼───┼───┤ abs: a = 0, b = 3
+ // │ │ │ │ │
+ // ├───┼───┼───┼───┤
+ // │ │ │ │ │
+ // ├───┼───┼───┼───┤
+ // │ │ │ │ b │
+ // └───┴───┴───┴───┘
+ let at = coords.map((v, i) => v * textureSize[i] - 0.5);
+
+ // Apply offset in whole texel units
+ if (call.offset !== undefined) {
+ at = add(at, toArray(call.offset));
+ }
+
+ const samples: { at: number[]; weight: number }[] = [];
+
+ const filter = sampler.minFilter;
+ switch (filter) {
+ case 'linear': {
+ // 'p0' is the lower texel for 'at'
+ const p0 = at.map(v => Math.floor(v));
+ // 'p1' is the higher texel for 'at'
+ const p1 = p0.map(v => v + 1);
+
+ // interpolation weights for p0 and p1
+ const p1W = at.map((v, i) => v - p0[i]);
+ const p0W = p1W.map(v => 1 - v);
+
+ switch (coords.length) {
+ case 1:
+ samples.push({ at: p0, weight: p0W[0] });
+ samples.push({ at: p1, weight: p1W[0] });
+ break;
+ case 2: {
+ samples.push({ at: p0, weight: p0W[0] * p0W[1] });
+ samples.push({ at: [p1[0], p0[1]], weight: p1W[0] * p0W[1] });
+ samples.push({ at: [p0[0], p1[1]], weight: p0W[0] * p1W[1] });
+ samples.push({ at: p1, weight: p1W[0] * p1W[1] });
+ break;
+ }
+ }
+ break;
+ }
+ case 'nearest': {
+ const p = at.map(v => Math.round(quantizeToF32(v)));
+ samples.push({ at: p, weight: 1 });
+ break;
+ }
+ default:
+ unreachable();
+ }
+
+ const out: PerTexelComponent<number> = {};
+ const ss = [];
+ for (const sample of samples) {
+ // Apply sampler address mode
+ const c = sample.at.map((v, i) => {
+ switch (addressMode[i]) {
+ case 'clamp-to-edge':
+ return clamp(v, { min: 0, max: textureSize[i] - 1 });
+ case 'mirror-repeat': {
+ const n = Math.floor(v / textureSize[i]);
+ v = v - n * textureSize[i];
+ return (n & 1) !== 0 ? textureSize[i] - v - 1 : v;
+ }
+ case 'repeat':
+ return v - Math.floor(v / textureSize[i]) * textureSize[i];
+ default:
+ unreachable();
+ }
+ });
+ const v = load(c);
+ ss.push(v);
+ for (const component of rep.componentOrder) {
+ out[component] = (out[component] ?? 0) + v[component]! * sample.weight;
+ }
+ }
+
+ return out;
+ }
+ case 'textureLoad': {
+ return load(toArray(call.coords!));
+ }
+ }
+}
+
+/**
+ * Puts random data in a texture, generates a shader that implements `calls`
+ * such that each call's result is written to the next consecutive texel of
+ * a rgba32float texture. It then checks the result of each call matches
+ * the expected result.
+ */
+export async function putDataInTextureThenDrawAndCheckResults<T extends Dimensionality>(
+ device: GPUDevice,
+ texture: Texture,
+ sampler: GPUSamplerDescriptor,
+ calls: TextureCall<T>[]
+) {
+ const results = await doTextureCalls(device, texture, sampler, calls);
+ const errs: string[] = [];
+ const rep = kTexelRepresentationInfo[texture.texels.format];
+ for (let callIdx = 0; callIdx < calls.length; callIdx++) {
+ const call = calls[callIdx];
+ const got = results[callIdx];
+ const expect = expected(call, texture, sampler);
+
+ const gULP = rep.bitsToULPFromZero(rep.numberToBits(got));
+ const eULP = rep.bitsToULPFromZero(rep.numberToBits(expect));
+ for (const component of rep.componentOrder) {
+ const g = got[component]!;
+ const e = expect[component]!;
+ const absDiff = Math.abs(g - e);
+ const ulpDiff = Math.abs(gULP[component]! - eULP[component]!);
+ const relDiff = absDiff / Math.max(Math.abs(g), Math.abs(e));
+ if (ulpDiff > 3 && relDiff > 0.03) {
+ const desc = describeTextureCall(call);
+ errs.push(`component was not as expected:
+ call: ${desc}
+ component: ${component}
+ got: ${g}
+ expected: ${e}
+ abs diff: ${absDiff.toFixed(4)}
+ rel diff: ${(relDiff * 100).toFixed(2)}%
+ ulp diff: ${ulpDiff}
+ sample points:
+`);
+ const expectedSamplePoints = [
+ 'expected:',
+ ...(await identifySamplePoints(texture.descriptor, (texels: TexelView) => {
+ return Promise.resolve(
+ expected(call, { texels, descriptor: texture.descriptor }, sampler)
+ );
+ })),
+ ];
+ const gotSamplePoints = [
+ 'got:',
+ ...(await identifySamplePoints(
+ texture.descriptor,
+ async (texels: TexelView) =>
+ (
+ await doTextureCalls(device, { texels, descriptor: texture.descriptor }, sampler, [
+ call,
+ ])
+ )[0]
+ )),
+ ];
+ errs.push(layoutTwoColumns(expectedSamplePoints, gotSamplePoints).join('\n'));
+ errs.push('', '');
+ }
+ }
+ }
+
+ return errs.length > 0 ? new Error(errs.join('\n')) : undefined;
+}
+
+/**
+ * Generates a text art grid showing which texels were sampled
+ * followed by a list of the samples and the weights used for each
+ * component.
+ *
+ * Example:
+ *
+ * 0 1 2 3 4 5 6 7
+ * ┌───┬───┬───┬───┬───┬───┬───┬───┐
+ * 0 │ │ │ │ │ │ │ │ │
+ * ├───┼───┼───┼───┼───┼───┼───┼───┤
+ * 1 │ │ │ │ │ │ │ │ a │
+ * ├───┼───┼───┼───┼───┼───┼───┼───┤
+ * 2 │ │ │ │ │ │ │ │ b │
+ * ├───┼───┼───┼───┼───┼───┼───┼───┤
+ * 3 │ │ │ │ │ │ │ │ │
+ * ├───┼───┼───┼───┼───┼───┼───┼───┤
+ * 4 │ │ │ │ │ │ │ │ │
+ * ├───┼───┼───┼───┼───┼───┼───┼───┤
+ * 5 │ │ │ │ │ │ │ │ │
+ * ├───┼───┼───┼───┼───┼───┼───┼───┤
+ * 6 │ │ │ │ │ │ │ │ │
+ * ├───┼───┼───┼───┼───┼───┼───┼───┤
+ * 7 │ │ │ │ │ │ │ │ │
+ * └───┴───┴───┴───┴───┴───┴───┴───┘
+ * a: at: [7, 1], weights: [R: 0.75000]
+ * b: at: [7, 2], weights: [R: 0.25000]
+ */
+async function identifySamplePoints(
+ info: GPUTextureDescriptor,
+ run: (texels: TexelView) => Promise<PerTexelComponent<number>>
+) {
+ const textureSize = reifyExtent3D(info.size);
+ const numTexels = textureSize.width * textureSize.height;
+ const rep = kTexelRepresentationInfo[info.format as EncodableTextureFormat];
+
+ // Identify all the texels that are sampled, and their weights.
+ const sampledTexelWeights = new Map<number, PerTexelComponent<number>>();
+ const unclassifiedStack = [new Set<number>(range(numTexels, v => v))];
+ while (unclassifiedStack.length > 0) {
+ // Pop the an unclassified texels stack
+ const unclassified = unclassifiedStack.pop()!;
+
+ // Split unclassified texels evenly into two new sets
+ const setA = new Set<number>();
+ const setB = new Set<number>();
+ [...unclassified.keys()].forEach((t, i) => ((i & 1) === 0 ? setA : setB).add(t));
+
+ // Push setB to the unclassified texels stack
+ if (setB.size > 0) {
+ unclassifiedStack.push(setB);
+ }
+
+ // See if any of the texels in setA were sampled.
+ const results = await run(
+ TexelView.fromTexelsAsColors(
+ info.format as EncodableTextureFormat,
+ (coords: Required<GPUOrigin3DDict>): Readonly<PerTexelComponent<number>> => {
+ const isCandidate = setA.has(coords.x + coords.y * textureSize.width);
+ const texel: PerTexelComponent<number> = {};
+ for (const component of rep.componentOrder) {
+ texel[component] = isCandidate ? 1 : 0;
+ }
+ return texel;
+ }
+ )
+ );
+ if (rep.componentOrder.some(c => results[c] !== 0)) {
+ // One or more texels of setA were sampled.
+ if (setA.size === 1) {
+ // We identified a specific texel was sampled.
+ // As there was only one texel in the set, results holds the sampling weights.
+ setA.forEach(texel => sampledTexelWeights.set(texel, results));
+ } else {
+ // More than one texel in the set. Needs splitting.
+ unclassifiedStack.push(setA);
+ }
+ }
+ }
+
+ // ┌───┬───┬───┬───┐
+ // │ a │ │ │ │
+ // ├───┼───┼───┼───┤
+ // │ │ │ │ │
+ // ├───┼───┼───┼───┤
+ // │ │ │ │ │
+ // ├───┼───┼───┼───┤
+ // │ │ │ │ b │
+ // └───┴───┴───┴───┘
+ const letter = (idx: number) => String.fromCharCode(97 + idx); // 97: 'a'
+ const orderedTexelIndices: number[] = [];
+ const lines: string[] = [];
+ {
+ let line = ' ';
+ for (let x = 0; x < textureSize.width; x++) {
+ line += ` ${x} `;
+ }
+ lines.push(line);
+ }
+ {
+ let line = ' ┌';
+ for (let x = 0; x < textureSize.width; x++) {
+ line += x === textureSize.width - 1 ? '───┐' : '───┬';
+ }
+ lines.push(line);
+ }
+ for (let y = 0; y < textureSize.height; y++) {
+ {
+ let line = `${y} │`;
+ for (let x = 0; x < textureSize.width; x++) {
+ const texelIdx = x + y * textureSize.height;
+ const weight = sampledTexelWeights.get(texelIdx);
+ if (weight !== undefined) {
+ line += ` ${letter(orderedTexelIndices.length)} │`;
+ orderedTexelIndices.push(texelIdx);
+ } else {
+ line += ' │';
+ }
+ }
+ lines.push(line);
+ }
+ if (y < textureSize.height - 1) {
+ let line = ' ├';
+ for (let x = 0; x < textureSize.width; x++) {
+ line += x === textureSize.width - 1 ? '───┤' : '───┼';
+ }
+ lines.push(line);
+ }
+ }
+ {
+ let line = ' └';
+ for (let x = 0; x < textureSize.width; x++) {
+ line += x === textureSize.width - 1 ? '───┘' : '───┴';
+ }
+ lines.push(line);
+ }
+
+ orderedTexelIndices.forEach((texelIdx, i) => {
+ const weights = sampledTexelWeights.get(texelIdx)!;
+ const y = Math.floor(texelIdx / textureSize.width);
+ const x = texelIdx - y * textureSize.height;
+ const w = rep.componentOrder.map(c => `${c}: ${weights[c]?.toFixed(5)}`).join(', ');
+ lines.push(`${letter(i)}: at: [${x}, ${y}], weights: [${w}]`);
+ });
+ return lines;
+}
+
+function layoutTwoColumns(columnA: string[], columnB: string[]) {
+ const widthA = Math.max(...columnA.map(l => l.length));
+ const lines = Math.max(columnA.length, columnB.length);
+ const out: string[] = new Array<string>(lines);
+ for (let line = 0; line < lines; line++) {
+ const a = columnA[line] ?? '';
+ const b = columnB[line] ?? '';
+ out[line] = `${a}${' '.repeat(widthA - a.length)} | ${b}`;
+ }
+ return out;
+}
+
+export const kSamplePointMethods = ['texel-centre', 'spiral'] as const;
+export type SamplePointMethods = (typeof kSamplePointMethods)[number];
+
+/**
+ * Generates an array of coordinates at which to sample a texture.
+ */
+export function generateSamplePoints(
+ n: number,
+ nearest: boolean,
+ args:
+ | {
+ method: 'texel-centre';
+ textureWidth: number;
+ textureHeight: number;
+ }
+ | {
+ method: 'spiral';
+ radius?: number;
+ loops?: number;
+ textureWidth: number;
+ textureHeight: number;
+ }
+) {
+ const out: vec2[] = [];
+ switch (args.method) {
+ case 'texel-centre': {
+ for (let i = 0; i < n; i++) {
+ const r = hashU32(i);
+ const x = Math.floor(lerp(0, args.textureWidth - 1, (r & 0xffff) / 0xffff)) + 0.5;
+ const y = Math.floor(lerp(0, args.textureHeight - 1, (r >>> 16) / 0xffff)) + 0.5;
+ out.push([x / args.textureWidth, y / args.textureHeight]);
+ }
+ break;
+ }
+ case 'spiral': {
+ for (let i = 0; i < n; i++) {
+ const f = i / (Math.max(n, 2) - 1);
+ const r = (args.radius ?? 1.5) * f;
+ const a = (args.loops ?? 2) * 2 * Math.PI * f;
+ out.push([0.5 + r * Math.cos(a), 0.5 + r * Math.sin(a)]);
+ }
+ break;
+ }
+ }
+ // Samplers across devices use different methods to interpolate.
+ // Quantizing the texture coordinates seems to hit coords that produce
+ // comparable results to our computed results.
+ // Note: This value works with 8x8 textures. Other sizes have not been tested.
+ // Values that worked for reference:
+ // Win 11, NVidia 2070 Super: 16
+ // Linux, AMD Radeon Pro WX 3200: 256
+ // MacOS, M1 Mac: 256
+ const kSubdivisionsPerTexel = 4;
+ const q = [args.textureWidth * kSubdivisionsPerTexel, args.textureHeight * kSubdivisionsPerTexel];
+ return out.map(
+ c =>
+ c.map((v, i) => {
+ // Quantize to kSubdivisionsPerPixel
+ const v1 = Math.floor(v * q[i]);
+ // If it's nearest and we're on the edge of a texel then move us off the edge
+ // since the edge could choose one texel or another in nearest mode
+ const v2 = nearest && v1 % kSubdivisionsPerTexel === 0 ? v1 + 1 : v1;
+ // Convert back to texture coords
+ return v2 / q[i];
+ }) as vec2
+ );
+}
+
+function wgslTypeFor(data: Dimensionality, type: 'f' | 'i' | 'u'): string {
+ if (data instanceof Array) {
+ switch (data.length) {
+ case 2:
+ return `vec2${type}`;
+ case 3:
+ return `vec3${type}`;
+ }
+ }
+ return '${type}32';
+}
+
+function wgslExpr(data: number | vec2 | vec3 | vec4): string {
+ if (data instanceof Array) {
+ switch (data.length) {
+ case 2:
+ return `vec2(${data.map(v => v.toString()).join(', ')})`;
+ case 3:
+ return `vec3(${data.map(v => v.toString()).join(', ')})`;
+ }
+ }
+ return data.toString();
+}
+
+function binKey<T extends Dimensionality>(call: TextureCall<T>): string {
+ const keys: string[] = [];
+ for (const name of kTextureCallArgNames) {
+ const value = call[name];
+ if (value !== undefined) {
+ if (name === 'offset') {
+ // offset must be a constant expression
+ keys.push(`${name}: ${wgslExpr(value)}`);
+ } else {
+ keys.push(`${name}: ${wgslTypeFor(value, call.coordType)}`);
+ }
+ }
+ }
+ return `${call.builtin}(${keys.join(', ')})`;
+}
+
+function buildBinnedCalls<T extends Dimensionality>(calls: TextureCall<T>[]) {
+ const args: string[] = ['T']; // All texture builtins take the texture as the first argument
+ const fields: string[] = [];
+ const data: number[] = [];
+
+ const prototype = calls[0];
+ if (prototype.builtin.startsWith('textureSample')) {
+ // textureSample*() builtins take a sampler as the second argument
+ args.push('S');
+ }
+
+ for (const name of kTextureCallArgNames) {
+ const value = prototype[name];
+ if (value !== undefined) {
+ if (name === 'offset') {
+ args.push(`/* offset */ ${wgslExpr(value)}`);
+ } else {
+ args.push(`args.${name}`);
+ fields.push(`@align(16) ${name} : ${wgslTypeFor(value, prototype.coordType)}`);
+ }
+ }
+ }
+
+ for (const call of calls) {
+ for (const name of kTextureCallArgNames) {
+ const value = call[name];
+ assert(
+ (prototype[name] === undefined) === (value === undefined),
+ 'texture calls are not binned correctly'
+ );
+ if (value !== undefined && name !== 'offset') {
+ const bitcastToU32 = (value: number) => {
+ if (calls[0].coordType === 'f') {
+ return float32ToUint32(value);
+ }
+ return value;
+ };
+ if (value instanceof Array) {
+ for (const c of value) {
+ data.push(bitcastToU32(c));
+ }
+ } else {
+ data.push(bitcastToU32(value));
+ }
+ // All fields are aligned to 16 bytes.
+ while ((data.length & 3) !== 0) {
+ data.push(0);
+ }
+ }
+ }
+ }
+
+ const expr = `${prototype.builtin}(${args.join(', ')})`;
+
+ return { expr, fields, data };
+}
+
+function binCalls<T extends Dimensionality>(calls: TextureCall<T>[]): number[][] {
+ const map = new Map<string, number>(); // key to bin index
+ const bins: number[][] = [];
+ calls.forEach((call, callIdx) => {
+ const key = binKey(call);
+ const binIdx = map.get(key);
+ if (binIdx === undefined) {
+ map.set(key, bins.length);
+ bins.push([callIdx]);
+ } else {
+ bins[binIdx].push(callIdx);
+ }
+ });
+ return bins;
+}
+
+export function describeTextureCall<T extends Dimensionality>(call: TextureCall<T>): string {
+ const args: string[] = ['texture: T'];
+ if (call.builtin.startsWith('textureSample')) {
+ args.push('sampler: S');
+ }
+ for (const name of kTextureCallArgNames) {
+ const value = call[name];
+ if (value !== undefined) {
+ args.push(`${name}: ${wgslExpr(value)}`);
+ }
+ }
+ return `${call.builtin}(${args.join(', ')})`;
+}
+
+/**
+ * Given a list of "calls", each one of which has a texture coordinate,
+ * generates a fragment shader that uses the fragment position as an index
+ * (position.y * 256 + position.x) That index is then used to look up a
+ * coordinate from a storage buffer which is used to call the WGSL texture
+ * function to read/sample the texture, and then write to an rgba32float
+ * texture. We then read the rgba32float texture for the per "call" results.
+ *
+ * Calls are "binned" by call parameters. Each bin has its own structure and
+ * field in the storage buffer. This allows the calls to be non-homogenous and
+ * each have their own data type for coordinates.
+ */
+export async function doTextureCalls<T extends Dimensionality>(
+ device: GPUDevice,
+ texture: Texture,
+ sampler: GPUSamplerDescriptor,
+ calls: TextureCall<T>[]
+) {
+ let structs = '';
+ let body = '';
+ let dataFields = '';
+ const data: number[] = [];
+ let callCount = 0;
+ const binned = binCalls(calls);
+ binned.forEach((binCalls, binIdx) => {
+ const b = buildBinnedCalls(binCalls.map(callIdx => calls[callIdx]));
+ structs += `struct Args${binIdx} {
+ ${b.fields.join(', \n')}
+}
+`;
+ dataFields += ` args${binIdx} : array<Args${binIdx}, ${binCalls.length}>,
+`;
+ body += `
+ {
+ let is_active = (frag_idx >= ${callCount}) & (frag_idx < ${callCount + binCalls.length});
+ let args = data.args${binIdx}[frag_idx - ${callCount}];
+ let call = ${b.expr};
+ result = select(result, call, is_active);
+ }
+`;
+ callCount += binCalls.length;
+ data.push(...b.data);
+ });
+
+ const dataBuffer = device.createBuffer({
+ size: data.length * 4,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
+ });
+ device.queue.writeBuffer(dataBuffer, 0, new Uint32Array(data));
+
+ const rtWidth = 256;
+ const renderTarget = device.createTexture({
+ format: 'rgba32float',
+ size: { width: rtWidth, height: Math.ceil(calls.length / rtWidth) },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+
+ const code = `
+${structs}
+
+struct Data {
+${dataFields}
+}
+
+@vertex
+fn vs_main(@builtin(vertex_index) vertex_index : u32) -> @builtin(position) vec4f {
+ let positions = array(
+ vec4f(-1, 1, 0, 1), vec4f( 1, 1, 0, 1),
+ vec4f(-1, -1, 0, 1), vec4f( 1, -1, 0, 1),
+ );
+ return positions[vertex_index];
+}
+
+@group(0) @binding(0) var T : texture_2d<f32>;
+@group(0) @binding(1) var S : sampler;
+@group(0) @binding(2) var<storage> data : Data;
+
+@fragment
+fn fs_main(@builtin(position) frag_pos : vec4f) -> @location(0) vec4f {
+ let frag_idx = u32(frag_pos.x) + u32(frag_pos.y) * ${renderTarget.width};
+ var result : vec4f;
+${body}
+ return result;
+}
+`;
+ const shaderModule = device.createShaderModule({ code });
+
+ const pipeline = device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module: shaderModule, entryPoint: 'vs_main' },
+ fragment: {
+ module: shaderModule,
+ entryPoint: 'fs_main',
+ targets: [{ format: renderTarget.format }],
+ },
+ primitive: { topology: 'triangle-strip', cullMode: 'none' },
+ });
+
+ const gpuTexture = createTextureFromTexelView(device, texture.texels, texture.descriptor);
+ const gpuSampler = device.createSampler(sampler);
+
+ const bindGroup = device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: gpuTexture.createView() },
+ { binding: 1, resource: gpuSampler },
+ { binding: 2, resource: { buffer: dataBuffer } },
+ ],
+ });
+
+ const bytesPerRow = align(16 * renderTarget.width, 256);
+ const resultBuffer = device.createBuffer({
+ size: renderTarget.height * bytesPerRow,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
+ });
+ const encoder = device.createCommandEncoder();
+
+ const renderPass = encoder.beginRenderPass({
+ colorAttachments: [{ view: renderTarget.createView(), loadOp: 'clear', storeOp: 'store' }],
+ });
+
+ renderPass.setPipeline(pipeline);
+ renderPass.setBindGroup(0, bindGroup);
+ renderPass.draw(4);
+ renderPass.end();
+ encoder.copyTextureToBuffer(
+ { texture: renderTarget },
+ { buffer: resultBuffer, bytesPerRow },
+ { width: renderTarget.width, height: renderTarget.height }
+ );
+ device.queue.submit([encoder.finish()]);
+
+ await resultBuffer.mapAsync(GPUMapMode.READ);
+
+ const view = TexelView.fromTextureDataByReference(
+ renderTarget.format as EncodableTextureFormat,
+ new Uint8Array(resultBuffer.getMappedRange()),
+ {
+ bytesPerRow,
+ rowsPerImage: renderTarget.height,
+ subrectOrigin: [0, 0, 0],
+ subrectSize: [renderTarget.width, renderTarget.height],
+ }
+ );
+
+ let outIdx = 0;
+ const out = new Array<PerTexelComponent<number>>(calls.length);
+ for (const bin of binned) {
+ for (const callIdx of bin) {
+ const x = outIdx % rtWidth;
+ const y = Math.floor(outIdx / rtWidth);
+ out[callIdx] = view.color({ x, y, z: 0 });
+ outIdx++;
+ }
+ }
+
+ renderTarget.destroy();
+ gpuTexture.destroy();
+ resultBuffer.destroy();
+
+ return out;
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.cache.ts
new file mode 100644
index 0000000000..cb89e2d194
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.cache.ts
@@ -0,0 +1,27 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]_matCxR_[non_]const
+// abstract_matCxR_non_const is empty and not used
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .flatMap(trait =>
+ ([2, 3, 4] as const).flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`${trait}_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ if (trait === 'abstract' && nonConst) {
+ return [];
+ }
+ return FP[trait].generateMatrixToMatrixCases(
+ FP[trait].sparseMatrixRange(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP[trait].transposeInterval
+ );
+ },
+ }))
+ )
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('transpose', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts
index 6fd4887f35..7a96906cfa 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts
@@ -1,82 +1,21 @@
export const description = `
Execution tests for the 'transpose' builtin function
-T is AbstractFloat, f32, or f16
+T is abstract-float, f32, or f16
@const transpose(e: matRxC<T> ) -> matCxR<T>
Returns the transpose of e.
`;
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeF16, TypeF32, TypeMat } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import {
- sparseMatrixF16Range,
- sparseMatrixF32Range,
- sparseMatrixF64Range,
-} from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './transpose.cache.js';
export const g = makeTestGroup(GPUTest);
-// Cases: f32_matCxR_[non_]const
-const f32_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`f32_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateMatrixToMatrixCases(
- sparseMatrixF32Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.transposeInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_matCxR_[non_]const
-const f16_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`f16_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f16.generateMatrixToMatrixCases(
- sparseMatrixF16Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.transposeInterval
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: abstract_matCxR
-const abstract_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).map(rows => ({
- [`abstract_mat${cols}x${rows}`]: () => {
- return FP.abstract.generateMatrixToMatrixCases(
- sparseMatrixF64Range(cols, rows),
- 'finite',
- FP.abstract.transposeInterval
- );
- },
- }))
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('transpose', {
- ...f32_cases,
- ...f16_cases,
- ...abstract_cases,
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions')
.desc(`abstract float tests`)
@@ -89,12 +28,12 @@ g.test('abstract_float')
.fn(async t => {
const cols = t.params.cols;
const rows = t.params.rows;
- const cases = await d.get(`abstract_mat${cols}x${rows}`);
+ const cases = await d.get(`abstract_mat${cols}x${rows}_const`);
await run(
t,
- abstractBuiltin('transpose'),
- [TypeMat(cols, rows, TypeAbstractFloat)],
- TypeMat(rows, cols, TypeAbstractFloat),
+ abstractFloatBuiltin('transpose'),
+ [Type.mat(cols, rows, Type.abstractFloat)],
+ Type.mat(rows, cols, Type.abstractFloat),
t.params,
cases
);
@@ -120,8 +59,8 @@ g.test('f32')
await run(
t,
builtin('transpose'),
- [TypeMat(cols, rows, TypeF32)],
- TypeMat(rows, cols, TypeF32),
+ [Type.mat(cols, rows, Type.f32)],
+ Type.mat(rows, cols, Type.f32),
t.params,
cases
);
@@ -150,8 +89,8 @@ g.test('f16')
await run(
t,
builtin('transpose'),
- [TypeMat(cols, rows, TypeF16)],
- TypeMat(rows, cols, TypeF16),
+ [Type.mat(cols, rows, Type.f16)],
+ Type.mat(rows, cols, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.cache.ts
new file mode 100644
index 0000000000..061c95b07f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.cache.ts
@@ -0,0 +1,17 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+// Cases: [f32|f16|abstract]
+const cases = (['f32', 'f16', 'abstract'] as const)
+ .map(trait => ({
+ [`${trait}`]: () => {
+ return FP[trait].generateScalarToIntervalCases(
+ FP[trait].scalarRange(),
+ 'unfiltered',
+ FP[trait].truncInterval
+ );
+ },
+ }))
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('trunc', cases);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts
index 63cd8470f5..9d05709fc6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts
@@ -1,7 +1,7 @@
export const description = `
Execution tests for the 'trunc' builtin function
-S is AbstractFloat, f32, f16
+S is abstract-float, f32, f16
T is S or vecN<S>
@const fn trunc(e: T ) -> T
Returns the nearest whole number whose absolute value is less than or equal to e.
@@ -10,32 +10,14 @@ Component-wise when T is a vector.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullF32Range, fullF64Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, onlyConstInputSource, run } from '../../expression.js';
-import { abstractBuiltin, builtin } from './builtin.js';
+import { abstractFloatBuiltin, builtin } from './builtin.js';
+import { d } from './trunc.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('trunc', {
- f32: () => {
- return FP.f32.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f32.truncInterval);
- },
- f16: () => {
- return FP.f16.generateScalarToIntervalCases(fullF32Range(), 'unfiltered', FP.f16.truncInterval);
- },
- abstract: () => {
- return FP.abstract.generateScalarToIntervalCases(
- fullF64Range(),
- 'unfiltered',
- FP.abstract.truncInterval
- );
- },
-});
-
g.test('abstract_float')
.specURL('https://www.w3.org/TR/WGSL/#float-builtin-functions')
.desc(`abstract float tests`)
@@ -46,7 +28,14 @@ g.test('abstract_float')
)
.fn(async t => {
const cases = await d.get('abstract');
- await run(t, abstractBuiltin('trunc'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases);
+ await run(
+ t,
+ abstractFloatBuiltin('trunc'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases
+ );
});
g.test('f32')
@@ -57,7 +46,7 @@ g.test('f32')
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, builtin('trunc'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, builtin('trunc'), [Type.f32], Type.f32, t.params, cases);
});
g.test('f16')
@@ -71,5 +60,5 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, builtin('trunc'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, builtin('trunc'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.cache.ts
new file mode 100644
index 0000000000..79a7a568d2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.cache.ts
@@ -0,0 +1,20 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+export const d = makeCaseCache('unpack2x16float', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack2x16floatInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack2x16floatInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts
index 4a0bf075e9..e145afca50 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16float.spec.ts
@@ -7,33 +7,14 @@ interpretation of bits 16×i through 16×i+15 of e as an IEEE-754 binary16 value
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullU32Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
+import { d } from './unpack2x16float.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unpack2x16float', {
- u32_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'finite',
- FP.f32.unpack2x16floatInterval
- );
- },
- u32_non_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'unfiltered',
- FP.f32.unpack2x16floatInterval
- );
- },
-});
-
g.test('unpack')
.specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions')
.desc(
@@ -44,5 +25,5 @@ g.test('unpack')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
- await run(t, builtin('unpack2x16float'), [TypeU32], TypeVec(2, TypeF32), t.params, cases);
+ await run(t, builtin('unpack2x16float'), [Type.u32], Type.vec2f, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.cache.ts
new file mode 100644
index 0000000000..89dfb475d3
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.cache.ts
@@ -0,0 +1,20 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+export const d = makeCaseCache('unpack2x16snorm', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack2x16snormInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack2x16snormInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts
index 195cfd9a01..059a5664f9 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16snorm.spec.ts
@@ -7,33 +7,14 @@ of bits 16×i through 16×i+15 of e as a twos-complement signed integer.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullU32Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
+import { d } from './unpack2x16snorm.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unpack2x16snorm', {
- u32_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'finite',
- FP.f32.unpack2x16snormInterval
- );
- },
- u32_non_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'unfiltered',
- FP.f32.unpack2x16snormInterval
- );
- },
-});
-
g.test('unpack')
.specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions')
.desc(
@@ -44,5 +25,5 @@ g.test('unpack')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
- await run(t, builtin('unpack2x16snorm'), [TypeU32], TypeVec(2, TypeF32), t.params, cases);
+ await run(t, builtin('unpack2x16snorm'), [Type.u32], Type.vec2f, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.cache.ts
new file mode 100644
index 0000000000..7f0fe84af6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.cache.ts
@@ -0,0 +1,20 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+export const d = makeCaseCache('unpack2x16unorm', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack2x16unormInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack2x16unormInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts
index 16b4e6397c..971f384023 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack2x16unorm.spec.ts
@@ -7,33 +7,14 @@ Component i of the result is v ÷ 65535, where v is the interpretation of bits
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullU32Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
+import { d } from './unpack2x16unorm.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unpack2x16unorm', {
- u32_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'finite',
- FP.f32.unpack2x16unormInterval
- );
- },
- u32_non_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'unfiltered',
- FP.f32.unpack2x16unormInterval
- );
- },
-});
-
g.test('unpack')
.specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions')
.desc(
@@ -44,5 +25,5 @@ g.test('unpack')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
- await run(t, builtin('unpack2x16unorm'), [TypeU32], TypeVec(2, TypeF32), t.params, cases);
+ await run(t, builtin('unpack2x16unorm'), [Type.u32], Type.vec2f, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.cache.ts
new file mode 100644
index 0000000000..3a4790f188
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.cache.ts
@@ -0,0 +1,20 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+export const d = makeCaseCache('unpack4x8snorm', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack4x8snormInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack4x8snormInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts
index 7ea8d51918..d6e533621b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8snorm.spec.ts
@@ -7,33 +7,14 @@ bits 8×i through 8×i+7 of e as a twos-complement signed integer.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullU32Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
+import { d } from './unpack4x8snorm.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unpack4x8snorm', {
- u32_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'finite',
- FP.f32.unpack4x8snormInterval
- );
- },
- u32_non_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'unfiltered',
- FP.f32.unpack4x8snormInterval
- );
- },
-});
-
g.test('unpack')
.specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions')
.desc(
@@ -44,5 +25,5 @@ g.test('unpack')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
- await run(t, builtin('unpack4x8snorm'), [TypeU32], TypeVec(4, TypeF32), t.params, cases);
+ await run(t, builtin('unpack4x8snorm'), [Type.u32], Type.vec4f, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.cache.ts
new file mode 100644
index 0000000000..21390f74b1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.cache.ts
@@ -0,0 +1,20 @@
+import { FP } from '../../../../../util/floating_point.js';
+import { fullU32Range } from '../../../../../util/math.js';
+import { makeCaseCache } from '../../case_cache.js';
+
+export const d = makeCaseCache('unpack4x8unorm', {
+ u32_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'finite',
+ FP.f32.unpack4x8unormInterval
+ );
+ },
+ u32_non_const: () => {
+ return FP.f32.generateU32ToIntervalCases(
+ fullU32Range(),
+ 'unfiltered',
+ FP.f32.unpack4x8unormInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts
index bf54d23c12..026043da1a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4x8unorm.spec.ts
@@ -7,33 +7,14 @@ through 8×i+7 of e as an unsigned integer.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../../gpu_test.js';
-import { TypeF32, TypeU32, TypeVec } from '../../../../../util/conversion.js';
-import { FP } from '../../../../../util/floating_point.js';
-import { fullU32Range } from '../../../../../util/math.js';
-import { makeCaseCache } from '../../case_cache.js';
+import { Type } from '../../../../../util/conversion.js';
import { allInputSources, run } from '../../expression.js';
import { builtin } from './builtin.js';
+import { d } from './unpack4x8unorm.cache.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unpack4x8unorm', {
- u32_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'finite',
- FP.f32.unpack4x8unormInterval
- );
- },
- u32_non_const: () => {
- return FP.f32.generateU32ToIntervalCases(
- fullU32Range(),
- 'unfiltered',
- FP.f32.unpack4x8unormInterval
- );
- },
-});
-
g.test('unpack')
.specURL('https://www.w3.org/TR/WGSL/#unpack-builtin-functions')
.desc(
@@ -44,5 +25,5 @@ g.test('unpack')
.params(u => u.combine('inputSource', allInputSources))
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
- await run(t, builtin('unpack4x8unorm'), [TypeU32], TypeVec(4, TypeF32), t.params, cases);
+ await run(t, builtin('unpack4x8unorm'), [Type.u32], Type.vec4f, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xI8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xI8.spec.ts
new file mode 100644
index 0000000000..4f7e4ed3a7
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xI8.spec.ts
@@ -0,0 +1,56 @@
+export const description = `
+Execution tests for the 'unpack4xI8' builtin function
+
+@const fn unpack4xI8(e: u32) -> vec4<i32>
+e is interpreted as a vector with four 8-bit signed integer components. Unpack e into a vec4<i32>
+with sign extension.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { u32, toVector, i32, Type } from '../../../../../util/conversion.js';
+import { Case } from '../../case.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('basic')
+ .specURL('https://www.w3.org/TR/WGSL/#unpack4xI8-builtin')
+ .desc(
+ `
+@const fn unpack4xI8(e: u32) -> vec4<i32>
+ `
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cfg: Config = t.params;
+
+ const unpack4xI8 = (e: number) => {
+ const result: [number, number, number, number] = [0, 0, 0, 0];
+ for (let i = 0; i < 4; ++i) {
+ let intValue = (e >> (8 * i)) & 0xff;
+ if (intValue > 127) {
+ intValue -= 256;
+ }
+ result[i] = intValue;
+ }
+ return result;
+ };
+
+ const testInputs = [
+ 0, 0x01020304, 0xfcfdfeff, 0x040302ff, 0x0403fe01, 0x04fd0201, 0xfc030201, 0xfcfdfe01,
+ 0xfcfd02ff, 0xfc03feff, 0x04fdfeff, 0x0403feff, 0x04fd02ff, 0xfc0302ff, 0x04fdfe01,
+ 0xfc03fe01, 0xfcfd0201, 0x80817f7e,
+ ] as const;
+
+ const makeCase = (e: number): Case => {
+ return { input: [u32(e)], expected: toVector(unpack4xI8(e), i32) };
+ };
+ const cases: Array<Case> = testInputs.flatMap(v => {
+ return [makeCase(v)];
+ });
+
+ await run(t, builtin('unpack4xI8'), [Type.u32], Type.vec4i, cfg, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xU8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xU8.spec.ts
new file mode 100644
index 0000000000..09849030eb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/unpack4xU8.spec.ts
@@ -0,0 +1,48 @@
+export const description = `
+Execution tests for the 'unpack4xU8' builtin function
+
+@const fn unpack4xU8(e: u32) -> vec4<u32>
+e is interpreted as a vector with four 8-bit unsigned integer components. Unpack e into a vec4<u32>
+with zero extension.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { u32, toVector, Type } from '../../../../../util/conversion.js';
+import { Case } from '../../case.js';
+import { allInputSources, Config, run } from '../../expression.js';
+
+import { builtin } from './builtin.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('basic')
+ .specURL('https://www.w3.org/TR/WGSL/#unpack4xU8-builtin')
+ .desc(
+ `
+@const fn unpack4xU8(e: u32) -> vec4<u32>
+ `
+ )
+ .params(u => u.combine('inputSource', allInputSources))
+ .fn(async t => {
+ const cfg: Config = t.params;
+
+ const unpack4xU8 = (e: number) => {
+ const result: [number, number, number, number] = [0, 0, 0, 0];
+ for (let i = 0; i < 4; ++i) {
+ result[i] = (e >> (8 * i)) & 0xff;
+ }
+ return result;
+ };
+
+ const testInputs = [0, 0x08060402, 0xffffffff, 0xfefdfcfb] as const;
+
+ const makeCase = (e: number): Case => {
+ return { input: [u32(e)], expected: toVector(unpack4xU8(e), u32) };
+ };
+ const cases: Array<Case> = testInputs.flatMap(v => {
+ return [makeCase(v)];
+ });
+
+ await run(t, builtin('unpack4xU8'), [Type.u32], Type.vec4u, cfg, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/workgroupUniformLoad.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/workgroupUniformLoad.spec.ts
new file mode 100644
index 0000000000..099b54146d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/builtin/workgroupUniformLoad.spec.ts
@@ -0,0 +1,182 @@
+export const description = `
+Executes a control barrier synchronization function that affects memory and atomic operations in the workgroup address space.
+`;
+
+// NOTE: The control barrier executed by this builtin is tested in the memory_model tests.
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import {
+ TypedArrayBufferView,
+ TypedArrayBufferViewConstructor,
+ iterRange,
+} from '../../../../../../common/util/util.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+import { checkElementsEqualGenerated } from '../../../../../util/check_contents.js';
+
+export const g = makeTestGroup(GPUTest);
+
+interface TypeConfig {
+ // The value to store the workgroup variable.
+ store_val: string;
+ // The expected values once the variable has been copied back to the host.
+ expected: TypedArrayBufferView;
+ // The type used for the host-visible buffer, if different from the workgroup variable.
+ host_type?: string;
+ // A type conversion function, if the types are different.
+ to_host?: (x: string) => string;
+ // Any additional module-scope declarations needed by the type.
+ decls?: string;
+}
+
+// A list of types configurations used for the workgroup variable.
+const kTypes: Record<string, TypeConfig> = {
+ bool: {
+ store_val: `true`,
+ expected: new Uint32Array([1]),
+ host_type: 'u32',
+ to_host: (x: string) => `u32(${x})`,
+ },
+ u32: {
+ store_val: `42`,
+ expected: new Uint32Array([42]),
+ },
+ vec4u: {
+ store_val: `vec4u(42, 1, 0xffffffff, 777)`,
+ expected: new Uint32Array([42, 1, 0xffffffff, 777]),
+ },
+ mat3x2f: {
+ store_val: `mat3x2(42, 1, 65536, -42, -1, -65536)`,
+ expected: new Float32Array([42, 1, 65536, -42, -1, -65536]),
+ },
+ 'array<u32, 4>': {
+ store_val: `array(42, 1, 0xffffffff, 777)`,
+ expected: new Uint32Array([42, 1, 0xffffffff, 777]),
+ },
+ SimpleStruct: {
+ decls: 'struct SimpleStruct { a: u32, b: u32, c: u32, d: u32, }',
+ store_val: `SimpleStruct(42, 1, 0xffffffff, 777)`,
+ expected: new Uint32Array([42, 1, 0xffffffff, 777]),
+ },
+ ComplexStruct: {
+ decls: `struct Inner { v: vec4u, }
+ struct ComplexStruct {
+ a: array<Inner, 4>,
+ @size(28) b: vec4u,
+ c: u32
+ }
+ const v = vec4(42, 1, 0xffffffff, 777);
+ const rhs = ComplexStruct(
+ array(Inner(v.xyzw), Inner(v.yzwx), Inner(v.zwxy), Inner(v.wxyz)),
+ v.xzxz,
+ 0x12345678,
+ );`,
+ store_val: `rhs`,
+ expected: new Uint32Array([
+ // v.xyzw
+ 42, 1, 0xffffffff, 777,
+ // v.yzwx
+ 1, 0xffffffff, 777, 42,
+ // v.zwxy
+ 0xffffffff, 777, 42, 1,
+ // v.wxyz
+ 777, 42, 1, 0xffffffff,
+ // v.xzxz
+ 42, 0xffffffff, 42, 0xffffffff,
+ // 12 bytes of padding
+ 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0x12345678,
+ ]),
+ },
+};
+
+g.test('types')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#workgroupUniformLoad-builtin')
+ .desc(
+ `Test that the result of a workgroupUniformLoad is the value previously stored to the workgroup variable, for a variety of types.
+ `
+ )
+ .params(u =>
+ u.combine('type', keysOf(kTypes)).combine('wgsize', [
+ [1, 1],
+ [3, 7],
+ [1, 128],
+ [16, 16],
+ ])
+ )
+ .fn(t => {
+ const type = kTypes[t.params.type];
+ const wgsize_x = t.params.wgsize[0];
+ const wgsize_y = t.params.wgsize[1];
+ const num_invocations = wgsize_x * wgsize_y;
+ const num_words_per_invocation = type.expected.length;
+ const total_host_words = num_invocations * num_words_per_invocation;
+
+ t.skipIf(
+ num_invocations > t.device.limits.maxComputeInvocationsPerWorkgroup,
+ `num_invocations (${num_invocations}) > maxComputeInvocationsPerWorkgroup (${t.device.limits.maxComputeInvocationsPerWorkgroup})`
+ );
+
+ let load = `workgroupUniformLoad(&wgvar)`;
+ if (type.to_host) {
+ load = type.to_host(load);
+ }
+
+ // Construct a shader that stores a value to workgroup variable and then loads it using
+ // workgroupUniformLoad() in every invocation, copying the results back to a storage buffer.
+ const code = `
+ ${type.decls ? type.decls : ''}
+
+ @group(0) @binding(0) var<storage, read_write> buffer : array<${
+ type.host_type ? type.host_type : t.params.type
+ }, ${num_invocations}>;
+
+ var<workgroup> wgvar : ${t.params.type};
+
+ @compute @workgroup_size(${wgsize_x}, ${wgsize_y})
+ fn main(@builtin(local_invocation_index) lid: u32) {
+ if (lid == ${num_invocations - 1}) {
+ wgvar = ${type.store_val};
+ }
+ buffer[lid] = ${load};
+ }
+ `;
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code }),
+ entryPoint: 'main',
+ },
+ });
+
+ // Allocate a buffer and fill it with 0xdeadbeef values.
+ const outputBuffer = t.makeBufferWithContents(
+ new Uint32Array([...iterRange(total_host_words, _i => 0xdeadbeef)]),
+ GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ );
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: outputBuffer } }],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Check that the output matches the expected values for each invocation.
+ t.expectGPUBufferValuesPassCheck(
+ outputBuffer,
+ data =>
+ checkElementsEqualGenerated(data, i => {
+ return Number(type.expected[i % num_words_per_invocation]);
+ }),
+ {
+ type: type.expected.constructor as TypedArrayBufferViewConstructor,
+ typedLength: total_host_words,
+ }
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/user/ptr_params.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/user/ptr_params.spec.ts
new file mode 100644
index 0000000000..87c3b9f2e1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/call/user/ptr_params.spec.ts
@@ -0,0 +1,849 @@
+export const description = `
+User function call tests for pointer parameters.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+function wgslTypeDecl(kind: 'vec4i' | 'array' | 'struct') {
+ switch (kind) {
+ case 'vec4i':
+ return `
+alias T = vec4i;
+`;
+ case 'array':
+ return `
+alias T = array<vec4f, 3>;
+`;
+ case 'struct':
+ return `
+struct S {
+a : i32,
+b : u32,
+c : i32,
+d : u32,
+}
+alias T = S;
+`;
+ }
+}
+
+function valuesForType(kind: 'vec4i' | 'array' | 'struct') {
+ switch (kind) {
+ case 'vec4i':
+ return new Uint32Array([1, 2, 3, 4]);
+ case 'array':
+ return new Float32Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
+ case 'struct':
+ return new Uint32Array([1, 2, 3, 4]);
+ }
+}
+
+function run(
+ t: GPUTest,
+ wgsl: string,
+ inputUsage: 'uniform' | 'storage',
+ input: Uint32Array | Float32Array,
+ expected: Uint32Array | Float32Array
+) {
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const inputBuffer = t.makeBufferWithContents(
+ input,
+ inputUsage === 'uniform' ? GPUBufferUsage.UNIFORM : GPUBufferUsage.STORAGE
+ );
+
+ const outputBuffer = t.device.createBuffer({
+ size: expected.buffer.byteLength,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ { binding: 0, resource: { buffer: inputBuffer } },
+ { binding: 1, resource: { buffer: outputBuffer } },
+ ],
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(outputBuffer, expected);
+}
+
+g.test('read_full_object')
+ .desc('Test a pointer parameter can be read by a callee function')
+ .params(u =>
+ u
+ .combine('address_space', ['function', 'private', 'workgroup', 'storage', 'uniform'] as const)
+ .combine('call_indirection', [0, 1, 2] as const)
+ .combine('type', ['vec4i', 'array', 'struct'] as const)
+ )
+ .fn(t => {
+ switch (t.params.address_space) {
+ case 'workgroup':
+ case 'storage':
+ case 'uniform':
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+ }
+
+ const main: string = {
+ function: `
+@compute @workgroup_size(1)
+fn main() {
+ var F : T = input;
+ f0(&F);
+}
+`,
+ private: `
+var<private> P : T;
+@compute @workgroup_size(1)
+fn main() {
+ P = input;
+ f0(&P);
+}
+`,
+ workgroup: `
+var<workgroup> W : T;
+@compute @workgroup_size(1)
+fn main() {
+ W = input;
+ f0(&W);
+}
+`,
+ storage: `
+@compute @workgroup_size(1)
+fn main() {
+ f0(&input);
+}
+`,
+ uniform: `
+@compute @workgroup_size(1)
+fn main() {
+ f0(&input);
+}
+`,
+ }[t.params.address_space];
+
+ let call_chain = '';
+ for (let i = 0; i < t.params.call_indirection; i++) {
+ call_chain += `
+fn f${i}(p : ptr<${t.params.address_space}, T>) {
+ f${i + 1}(p);
+}
+`;
+ }
+
+ const inputVar: string =
+ t.params.address_space === 'uniform'
+ ? `@binding(0) @group(0) var<uniform> input : T;`
+ : `@binding(0) @group(0) var<storage, read> input : T;`;
+
+ const wgsl = `
+${wgslTypeDecl(t.params.type)}
+
+${inputVar}
+
+@binding(1) @group(0) var<storage, read_write> output : T;
+
+fn f${t.params.call_indirection}(p : ptr<${t.params.address_space}, T>) {
+ output = *p;
+}
+
+${call_chain}
+
+${main}
+`;
+
+ const values = valuesForType(t.params.type);
+
+ run(t, wgsl, t.params.address_space === 'uniform' ? 'uniform' : 'storage', values, values);
+ });
+
+g.test('read_ptr_to_member')
+ .desc('Test a pointer parameter to a member of a structure can be read by a callee function')
+ .params(u =>
+ u.combine('address_space', ['function', 'private', 'workgroup', 'storage', 'uniform'] as const)
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const main: string = {
+ function: `
+@compute @workgroup_size(1)
+fn main() {
+ var v : S = input;
+ output = f0(&v);
+}
+`,
+ private: `
+var<private> P : S;
+@compute @workgroup_size(1)
+fn main() {
+ P = input;
+ output = f0(&P);
+}
+`,
+ workgroup: `
+var<workgroup> W : S;
+@compute @workgroup_size(1)
+fn main() {
+ W = input;
+ output = f0(&W);
+}
+`,
+ storage: `
+@compute @workgroup_size(1)
+fn main() {
+ output = f0(&input);
+}
+`,
+ uniform: `
+@compute @workgroup_size(1)
+fn main() {
+ output = f0(&input);
+}
+`,
+ }[t.params.address_space];
+
+ const inputVar: string =
+ t.params.address_space === 'uniform'
+ ? `@binding(0) @group(0) var<uniform> input : S;`
+ : `@binding(0) @group(0) var<storage, read> input : S;`;
+
+ const wgsl = `
+struct S {
+ a : vec4i,
+ b : T,
+ c : vec4i,
+}
+
+struct T {
+ a : vec4i,
+ b : vec4i,
+}
+
+
+${inputVar}
+@binding(1) @group(0) var<storage, read_write> output : T;
+
+fn f2(p : ptr<${t.params.address_space}, T>) -> T {
+ return *p;
+}
+
+fn f1(p : ptr<${t.params.address_space}, S>) -> T {
+ return f2(&(*p).b);
+}
+
+fn f0(p : ptr<${t.params.address_space}, S>) -> T {
+ return f1(p);
+}
+
+${main}
+`;
+
+ // prettier-ignore
+ const input = new Uint32Array([
+ /* S.a */ 1, 2, 3, 4,
+ /* S.b.a */ 5, 6, 7, 8,
+ /* S.b.b */ 9, 10, 11, 12,
+ /* S.c */ 13, 14, 15, 16,
+ ]);
+
+ // prettier-ignore
+ const expected = new Uint32Array([
+ /* S.b.a */ 5, 6, 7, 8,
+ /* S.b.b */ 9, 10, 11, 12,
+ ]);
+
+ run(t, wgsl, t.params.address_space === 'uniform' ? 'uniform' : 'storage', input, expected);
+ });
+
+g.test('read_ptr_to_element')
+ .desc('Test a pointer parameter to an element of an array can be read by a callee function')
+ .params(u =>
+ u.combine('address_space', ['function', 'private', 'workgroup', 'storage', 'uniform'] as const)
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const main: string = {
+ function: `
+@compute @workgroup_size(1)
+fn main() {
+ var v : T = input;
+ output = f0(&v);
+}
+`,
+ private: `
+var<private> P : T;
+@compute @workgroup_size(1)
+fn main() {
+ P = input;
+ output = f0(&P);
+}
+`,
+ workgroup: `
+var<workgroup> W : T;
+@compute @workgroup_size(1)
+fn main() {
+ W = input;
+ output = f0(&W);
+}
+`,
+ storage: `
+@compute @workgroup_size(1)
+fn main() {
+ output = f0(&input);
+}
+`,
+ uniform: `
+@compute @workgroup_size(1)
+fn main() {
+ output = f0(&input);
+}
+`,
+ }[t.params.address_space];
+
+ const inputVar: string =
+ t.params.address_space === 'uniform'
+ ? `@binding(0) @group(0) var<uniform> input : T;`
+ : `@binding(0) @group(0) var<storage, read> input : T;`;
+
+ const wgsl = `
+alias T3 = vec4i;
+alias T2 = array<T3, 2>;
+alias T1 = array<T2, 3>;
+alias T = array<T1, 2>;
+
+${inputVar}
+@binding(1) @group(0) var<storage, read_write> output : T3;
+
+fn f2(p : ptr<${t.params.address_space}, T2>) -> T3 {
+ return (*p)[1];
+}
+
+fn f1(p : ptr<${t.params.address_space}, T1>) -> T3 {
+ return f2(&(*p)[0]) + f2(&(*p)[2]);
+}
+
+fn f0(p : ptr<${t.params.address_space}, T>) -> T3 {
+ return f1(&(*p)[0]);
+}
+
+${main}
+`;
+
+ // prettier-ignore
+ const input = new Uint32Array([
+ /* [0][0][0] */ 1, 2, 3, 4,
+ /* [0][0][1] */ 5, 6, 7, 8,
+ /* [0][1][0] */ 9, 10, 11, 12,
+ /* [0][1][1] */ 13, 14, 15, 16,
+ /* [0][2][0] */ 17, 18, 19, 20,
+ /* [0][2][1] */ 21, 22, 23, 24,
+ /* [1][0][0] */ 25, 26, 27, 28,
+ /* [1][0][1] */ 29, 30, 31, 32,
+ /* [1][1][0] */ 33, 34, 35, 36,
+ /* [1][1][1] */ 37, 38, 39, 40,
+ /* [1][2][0] */ 41, 42, 43, 44,
+ /* [1][2][1] */ 45, 46, 47, 48,
+ ]);
+ const expected = new Uint32Array([/* [0][0][1] + [0][2][1] */ 5 + 21, 6 + 22, 7 + 23, 8 + 24]);
+
+ run(t, wgsl, t.params.address_space === 'uniform' ? 'uniform' : 'storage', input, expected);
+ });
+
+g.test('write_full_object')
+ .desc('Test a pointer parameter can be written to by a callee function')
+ .params(u =>
+ u
+ .combine('address_space', ['function', 'private', 'workgroup', 'storage'] as const)
+ .combine('call_indirection', [0, 1, 2] as const)
+ .combine('type', ['vec4i', 'array', 'struct'] as const)
+ )
+ .fn(t => {
+ switch (t.params.address_space) {
+ case 'workgroup':
+ case 'storage':
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+ }
+
+ const ptr =
+ t.params.address_space === 'storage'
+ ? `ptr<storage, T, read_write>`
+ : `ptr<${t.params.address_space}, T>`;
+
+ const main: string = {
+ function: `
+@compute @workgroup_size(1)
+fn main() {
+ var F : T;
+ f0(&F);
+ output = F;
+}
+`,
+ private: `
+var<private> P : T;
+@compute @workgroup_size(1)
+fn main() {
+ f0(&P);
+ output = P;
+}
+`,
+ workgroup: `
+var<workgroup> W : T;
+@compute @workgroup_size(1)
+fn main() {
+ f0(&W);
+ output = W;
+}
+`,
+ storage: `
+@compute @workgroup_size(1)
+fn main() {
+ f0(&output);
+}
+`,
+ }[t.params.address_space];
+
+ let call_chain = '';
+ for (let i = 0; i < t.params.call_indirection; i++) {
+ call_chain += `
+fn f${i}(p : ${ptr}) {
+ f${i + 1}(p);
+}
+`;
+ }
+
+ const wgsl = `
+${wgslTypeDecl(t.params.type)}
+
+@binding(0) @group(0) var<uniform> input : T;
+@binding(1) @group(0) var<storage, read_write> output : T;
+
+fn f${t.params.call_indirection}(p : ${ptr}) {
+ *p = input;
+}
+
+${call_chain}
+
+${main}
+`;
+
+ const values = valuesForType(t.params.type);
+
+ run(t, wgsl, 'uniform', values, values);
+ });
+
+g.test('write_ptr_to_member')
+ .desc(
+ 'Test a pointer parameter to a member of a structure can be written to by a callee function'
+ )
+ .params(u => u.combine('address_space', ['function', 'private', 'workgroup', 'storage'] as const))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const main: string = {
+ function: `
+@compute @workgroup_size(1)
+fn main() {
+ var v : S;
+ f0(&v);
+ output = v;
+}
+`,
+ private: `
+var<private> P : S;
+@compute @workgroup_size(1)
+fn main() {
+ f0(&P);
+ output = P;
+}
+`,
+ workgroup: `
+var<workgroup> W : S;
+@compute @workgroup_size(1)
+fn main() {
+ f0(&W);
+ output = W;
+}
+`,
+ storage: `
+@compute @workgroup_size(1)
+fn main() {
+ f1(&output);
+}
+`,
+ }[t.params.address_space];
+
+ const ptr = (ty: string) =>
+ t.params.address_space === 'storage'
+ ? `ptr<storage, ${ty}, read_write>`
+ : `ptr<${t.params.address_space}, ${ty}>`;
+
+ const wgsl = `
+struct S {
+ a : vec4i,
+ b : T,
+ c : vec4i,
+}
+
+struct T {
+ a : vec4i,
+ b : vec4i,
+}
+
+
+@binding(0) @group(0) var<storage> input : T;
+@binding(1) @group(0) var<storage, read_write> output : S;
+
+fn f2(p : ${ptr('T')}) {
+ *p = input;
+}
+
+fn f1(p : ${ptr('S')}) {
+ f2(&(*p).b);
+}
+
+fn f0(p : ${ptr('S')}) {
+ f1(p);
+}
+
+${main}
+`;
+
+ // prettier-ignore
+ const input = new Uint32Array([
+ /* S.b.a */ 5, 6, 7, 8,
+ /* S.b.b */ 9, 10, 11, 12,
+ ]);
+
+ // prettier-ignore
+ const expected = new Uint32Array([
+ /* S.a */ 0, 0, 0, 0,
+ /* S.b.a */ 5, 6, 7, 8,
+ /* S.b.b */ 9, 10, 11, 12,
+ /* S.c */ 0, 0, 0, 0,
+ ]);
+
+ run(t, wgsl, 'storage', input, expected);
+ });
+
+g.test('write_ptr_to_element')
+ .desc('Test a pointer parameter to an element of an array can be written to by a callee function')
+ .params(u => u.combine('address_space', ['function', 'private', 'workgroup', 'storage'] as const))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const main: string = {
+ function: `
+@compute @workgroup_size(1)
+fn main() {
+ var v : T;
+ f0(&v);
+ output = v;
+}
+`,
+ private: `
+var<private> P : T;
+@compute @workgroup_size(1)
+fn main() {
+ f0(&P);
+ output = P;
+}
+`,
+ workgroup: `
+var<workgroup> W : T;
+@compute @workgroup_size(1)
+fn main() {
+ f0(&W);
+ output = W;
+}
+`,
+ storage: `
+@compute @workgroup_size(1)
+fn main() {
+ f0(&output);
+}
+`,
+ }[t.params.address_space];
+
+ const ptr = (ty: string) =>
+ t.params.address_space === 'storage'
+ ? `ptr<storage, ${ty}, read_write>`
+ : `ptr<${t.params.address_space}, ${ty}>`;
+
+ const wgsl = `
+alias T3 = vec4i;
+alias T2 = array<T3, 2>;
+alias T1 = array<T2, 3>;
+alias T = array<T1, 2>;
+
+@binding(0) @group(0) var<storage, read> input : T3;
+@binding(1) @group(0) var<storage, read_write> output : T;
+
+fn f2(p : ${ptr('T2')}) {
+ (*p)[1] = input;
+}
+
+fn f1(p : ${ptr('T1')}) {
+ f2(&(*p)[0]);
+ f2(&(*p)[2]);
+}
+
+fn f0(p : ${ptr('T')}) {
+ f1(&(*p)[0]);
+}
+
+${main}
+`;
+
+ const input = new Uint32Array([1, 2, 3, 4]);
+
+ // prettier-ignore
+ const expected = new Uint32Array([
+ /* [0][0][0] */ 0, 0, 0, 0,
+ /* [0][0][1] */ 1, 2, 3, 4,
+ /* [0][1][0] */ 0, 0, 0, 0,
+ /* [0][1][1] */ 0, 0, 0, 0,
+ /* [0][2][0] */ 0, 0, 0, 0,
+ /* [0][2][1] */ 1, 2, 3, 4,
+ /* [1][0][0] */ 0, 0, 0, 0,
+ /* [1][0][1] */ 0, 0, 0, 0,
+ /* [1][1][0] */ 0, 0, 0, 0,
+ /* [1][1][1] */ 0, 0, 0, 0,
+ /* [1][2][0] */ 0, 0, 0, 0,
+ /* [1][2][1] */ 0, 0, 0, 0,
+ ]);
+
+ run(t, wgsl, 'storage', input, expected);
+ });
+
+g.test('atomic_ptr_to_element')
+ .desc(
+ 'Test a pointer parameter to an atomic<i32> of an array can be read from and written to by a callee function'
+ )
+ .params(u => u.combine('address_space', ['workgroup', 'storage'] as const))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const main: string = {
+ workgroup: `
+var<workgroup> W_input : T;
+var<workgroup> W_output : T;
+@compute @workgroup_size(1)
+fn main() {
+ // Copy input -> W_input
+ for (var i = 0; i < 2; i++) {
+ for (var j = 0; j < 3; j++) {
+ for (var k = 0; k < 2; k++) {
+ atomicStore(&W_input[k][j][i], atomicLoad(&input[k][j][i]));
+ }
+ }
+ }
+
+ f0(&W_input, &W_output);
+
+ // Copy W_output -> output
+ for (var i = 0; i < 2; i++) {
+ for (var j = 0; j < 3; j++) {
+ for (var k = 0; k < 2; k++) {
+ atomicStore(&output[k][j][i], atomicLoad(&W_output[k][j][i]));
+ }
+ }
+ }
+}
+`,
+ storage: `
+@compute @workgroup_size(1)
+fn main() {
+ f0(&input, &output);
+}
+`,
+ }[t.params.address_space];
+
+ const ptr = (ty: string) =>
+ t.params.address_space === 'storage'
+ ? `ptr<storage, ${ty}, read_write>`
+ : `ptr<${t.params.address_space}, ${ty}>`;
+
+ const wgsl = `
+alias T3 = atomic<i32>;
+alias T2 = array<T3, 2>;
+alias T1 = array<T2, 3>;
+alias T = array<T1, 2>;
+
+@binding(0) @group(0) var<storage, read_write> input : T;
+@binding(1) @group(0) var<storage, read_write> output : T;
+
+fn f2(in : ${ptr('T2')}, out : ${ptr('T2')}) {
+ let v = atomicLoad(&(*in)[0]);
+ atomicStore(&(*out)[1], v);
+}
+
+fn f1(in : ${ptr('T1')}, out : ${ptr('T1')}) {
+ f2(&(*in)[0], &(*out)[1]);
+ f2(&(*in)[2], &(*out)[0]);
+}
+
+fn f0(in : ${ptr('T')}, out : ${ptr('T')}) {
+ f1(&(*in)[1], &(*out)[0]);
+}
+
+${main}
+`;
+
+ // prettier-ignore
+ const input = new Uint32Array([
+ /* [0][0][0] */ 1,
+ /* [0][0][1] */ 2,
+ /* [0][1][0] */ 3,
+ /* [0][1][1] */ 4,
+ /* [0][2][0] */ 5,
+ /* [0][2][1] */ 6,
+ /* [1][0][0] */ 7, // -> [0][1][1]
+ /* [1][0][1] */ 8,
+ /* [1][1][0] */ 9,
+ /* [1][1][1] */ 10,
+ /* [1][2][0] */ 11, // -> [0][0][1]
+ /* [1][2][1] */ 12,
+ ]);
+
+ // prettier-ignore
+ const expected = new Uint32Array([
+ /* [0][0][0] */ 0,
+ /* [0][0][1] */ 11,
+ /* [0][1][0] */ 0,
+ /* [0][1][1] */ 7,
+ /* [0][2][0] */ 0,
+ /* [0][2][1] */ 0,
+ /* [1][0][0] */ 0,
+ /* [1][0][1] */ 0,
+ /* [1][1][0] */ 0,
+ /* [1][1][1] */ 0,
+ /* [1][2][0] */ 0,
+ /* [1][2][1] */ 0,
+ ]);
+
+ run(t, wgsl, 'storage', input, expected);
+ });
+
+g.test('array_length')
+ .desc(
+ 'Test a pointer parameter to a runtime sized array can be used by arrayLength() in a callee function'
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const wgsl = `
+@binding(0) @group(0) var<storage, read> arr : array<u32>;
+@binding(1) @group(0) var<storage, read_write> output : u32;
+
+fn f2(p : ptr<storage, array<u32>, read>) -> u32 {
+ return arrayLength(p);
+}
+
+fn f1(p : ptr<storage, array<u32>, read>) -> u32 {
+ return f2(p);
+}
+
+fn f0(p : ptr<storage, array<u32>, read>) -> u32 {
+ return f1(p);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ output = f0(&arr);
+}
+`;
+
+ const input = new Uint32Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
+ const expected = new Uint32Array([12]);
+
+ run(t, wgsl, 'storage', input, expected);
+ });
+
+g.test('mixed_ptr_parameters')
+ .desc('Test that functions can accept multiple, mixed pointer parameters')
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const wgsl = `
+@binding(0) @group(0) var<uniform> input : array<vec4i, 4>;
+@binding(1) @group(0) var<storage, read_write> output : array<vec4i, 4>;
+
+fn sum(f : ptr<function, i32>,
+ w : ptr<workgroup, atomic<i32>>,
+ p : ptr<private, i32>,
+ u : ptr<uniform, vec4i>) -> vec4i {
+
+ return vec4(*f + atomicLoad(w) + *p) + *u;
+}
+
+struct S {
+ i : i32,
+}
+
+var<private> P0 = S(0);
+var<private> P1 = S(10);
+var<private> P2 = 20;
+var<private> P3 = 30;
+
+struct T {
+ i : atomic<i32>,
+}
+
+var<workgroup> W0 : T;
+var<workgroup> W1 : atomic<i32>;
+var<workgroup> W2 : T;
+var<workgroup> W3 : atomic<i32>;
+
+@compute @workgroup_size(1)
+fn main() {
+ atomicStore(&W0.i, 0);
+ atomicStore(&W1, 100);
+ atomicStore(&W2.i, 200);
+ atomicStore(&W3, 300);
+
+ var F = array(0, 1000, 2000, 3000);
+
+ output[0] = sum(&F[2], &W3, &P1.i, &input[0]); // vec4(2310) + vec4(1, 2, 3, 4)
+ output[1] = sum(&F[1], &W2.i, &P0.i, &input[1]); // vec4(1200) + vec4(4, 3, 2, 1)
+ output[2] = sum(&F[3], &W0.i, &P3, &input[2]); // vec4(3030) + vec4(2, 4, 1, 3)
+ output[3] = sum(&F[2], &W1, &P2, &input[3]); // vec4(2120) + vec4(4, 1, 2, 3)
+}
+`;
+
+ // prettier-ignore
+ const input = new Uint32Array([
+ /* [0] */ 1, 2, 3, 4,
+ /* [1] */ 4, 3, 2, 1,
+ /* [2] */ 2, 4, 1, 3,
+ /* [3] */ 4, 1, 2, 3,
+ ]);
+
+ // prettier-ignore
+ const expected = new Uint32Array([
+ /* [0] */ 2311, 2312, 2313, 2314,
+ /* [1] */ 1204, 1203, 1202, 1201,
+ /* [2] */ 3032, 3034, 3031, 3033,
+ /* [3] */ 2124, 2121, 2122, 2123,
+ ]);
+
+ run(t, wgsl, 'uniform', input, expected);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/case.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/case.ts
new file mode 100644
index 0000000000..d837b1c32f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/case.ts
@@ -0,0 +1,440 @@
+import { crc32 } from '../../../../common/util/crc32.js';
+import { ROArrayArray } from '../../../../common/util/types.js';
+import { assert } from '../../../../common/util/util.js';
+import {
+ abstractInt,
+ i32,
+ ScalarBuilder,
+ u32,
+ Value,
+ VectorValue,
+} from '../../../util/conversion.js';
+import {
+ cartesianProduct,
+ QuantizeFunc,
+ quantizeToI32,
+ quantizeToI64,
+ quantizeToU32,
+} from '../../../util/math.js';
+
+import { Expectation } from './expectation.js';
+
+function notUndefined<T>(value: T | undefined): value is T {
+ return value !== undefined;
+}
+
+/** Case is a single expression test case. */
+export type Case = {
+ // The input value(s)
+ input: Value | ReadonlyArray<Value>;
+ // The expected result, or function to check the result
+ expected: Expectation;
+};
+
+/**
+ * Filters a given set of Cases down to a target number of cases by
+ * randomly selecting which Cases to return.
+ *
+ * The selection algorithm is deterministic and stable for a case's
+ * inputs.
+ *
+ * This means that if a specific case is selected is not affected by the
+ * presence of other cases in the list, so in theory it is possible to create a
+ * pathological set of cases such that all or not of the cases are selected
+ * in spite of the target number.
+ *
+ * This is a trade-off from guaranteeing stability of the selected cases over
+ * small changes, so the target number of cases is more of a suggestion. It is
+ * still guaranteed that if you set n0 < n1, then the invocation with n0 will
+ * return at most the number of cases that n1 does, it just isn't guaranteed to
+ * be less.
+ *
+ * @param dis is a string provided for additional hashing information to avoid
+ * systemic bias in the selection process across different test
+ * suites. Specifically every Case with the same input values being
+ * included or skipped regardless of the operation that they are
+ * testing. This string should be something like the name of the case
+ * cache the values are for or the operation under test.
+ * @param n number of cases targeted be returned. Expected to be a positive
+ * integer. If equal or greater than the number of cases, then all the
+ * cases are returned. 0 is not allowed, since it is likely a
+ * programming error, because if the caller intentionally wants 0
+ * items, they can just use [].
+ * @param cases list of Cases to be selected from.
+ */
+export function selectNCases(dis: string, n: number, cases: Case[]): Case[] {
+ assert(n > 0 && Math.round(n) === n, `n ${n} is expected to be a positive integer`);
+ const count = cases.length;
+ if (n >= count) {
+ return cases;
+ }
+ const dis_crc32 = crc32(dis);
+ return cases.filter(
+ c => Math.trunc((n / count) * 0xffff_ffff) > (crc32(c.input.toString()) ^ dis_crc32) >>> 0
+ );
+}
+
+/**
+ * A function that performs a binary operation on x and y, and returns the
+ * expected result.
+ */
+export interface BinaryOp<T> {
+ (x: T, y: T): T | undefined;
+}
+
+/**
+ * A function that performs a vector-vector operation on x and y, and returns the
+ * expected result.
+ */
+export interface VectorVectorToScalarOp<T> {
+ (x: T[], y: T[]): T | undefined;
+}
+
+/**
+ * @returns a Case for the input params with op applied
+ * @param scalar scalar param
+ * @param vector vector param (2, 3, or 4 elements)
+ * @param op the op to apply to scalar and vector
+ * @param quantize function to quantize all values in vectors and scalars
+ * @param scalarize function to convert numbers to Scalars
+ */
+function makeScalarVectorBinaryToVectorCase<T>(
+ scalar: T,
+ vector: readonly T[],
+ op: BinaryOp<T>,
+ quantize: QuantizeFunc<T>,
+ scalarize: ScalarBuilder<T>
+): Case | undefined {
+ scalar = quantize(scalar);
+ vector = vector.map(quantize);
+ const result = vector.map(v => op(scalar, v));
+ if (result.includes(undefined)) {
+ return undefined;
+ }
+ return {
+ input: [scalarize(scalar), new VectorValue(vector.map(scalarize))],
+ expected: new VectorValue(result.filter(notUndefined).map(scalarize)),
+ };
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param scalars array of scalar params
+ * @param vectors array of vector params (2, 3, or 4 elements)
+ * @param op the op to apply to each pair of scalar and vector
+ * @param quantize function to quantize all values in vectors and scalars
+ * @param scalarize function to convert numbers to Scalars
+ */
+function generateScalarVectorBinaryToVectorCases<T>(
+ scalars: readonly T[],
+ vectors: ROArrayArray<T>,
+ op: BinaryOp<T>,
+ quantize: QuantizeFunc<T>,
+ scalarize: ScalarBuilder<T>
+): Case[] {
+ return scalars.flatMap(s => {
+ return vectors
+ .map(v => {
+ return makeScalarVectorBinaryToVectorCase(s, v, op, quantize, scalarize);
+ })
+ .filter(notUndefined);
+ });
+}
+
+/**
+ * @returns a Case for the input params with op applied
+ * @param vector vector param (2, 3, or 4 elements)
+ * @param scalar scalar param
+ * @param op the op to apply to vector and scalar
+ * @param quantize function to quantize all values in vectors and scalars
+ * @param scalarize function to convert numbers to Scalars
+ */
+function makeVectorScalarBinaryToVectorCase<T>(
+ vector: readonly T[],
+ scalar: T,
+ op: BinaryOp<T>,
+ quantize: QuantizeFunc<T>,
+ scalarize: ScalarBuilder<T>
+): Case | undefined {
+ vector = vector.map(quantize);
+ scalar = quantize(scalar);
+ const result = vector.map(v => op(v, scalar));
+ if (result.includes(undefined)) {
+ return undefined;
+ }
+ return {
+ input: [new VectorValue(vector.map(scalarize)), scalarize(scalar)],
+ expected: new VectorValue(result.filter(notUndefined).map(scalarize)),
+ };
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param vectors array of vector params (2, 3, or 4 elements)
+ * @param scalars array of scalar params
+ * @param op the op to apply to each pair of vector and scalar
+ * @param quantize function to quantize all values in vectors and scalars
+ * @param scalarize function to convert numbers to Scalars
+ */
+function generateVectorScalarBinaryToVectorCases<T>(
+ vectors: ROArrayArray<T>,
+ scalars: readonly T[],
+ op: BinaryOp<T>,
+ quantize: QuantizeFunc<T>,
+ scalarize: ScalarBuilder<T>
+): Case[] {
+ return scalars.flatMap(s => {
+ return vectors
+ .map(v => {
+ return makeVectorScalarBinaryToVectorCase(v, s, op, quantize, scalarize);
+ })
+ .filter(notUndefined);
+ });
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param scalars array of scalar params
+ * @param vectors array of vector params (2, 3, or 4 elements)
+ * @param op he op to apply to each pair of scalar and vector
+ */
+export function generateU32VectorBinaryToVectorCases(
+ scalars: readonly number[],
+ vectors: ROArrayArray<number>,
+ op: BinaryOp<number>
+): Case[] {
+ return generateScalarVectorBinaryToVectorCases(scalars, vectors, op, quantizeToU32, u32);
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param vectors array of vector params (2, 3, or 4 elements)
+ * @param scalars array of scalar params
+ * @param op he op to apply to each pair of vector and scalar
+ */
+export function generateVectorU32BinaryToVectorCases(
+ vectors: ROArrayArray<number>,
+ scalars: readonly number[],
+ op: BinaryOp<number>
+): Case[] {
+ return generateVectorScalarBinaryToVectorCases(vectors, scalars, op, quantizeToU32, u32);
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param scalars array of scalar params
+ * @param vectors array of vector params (2, 3, or 4 elements)
+ * @param op he op to apply to each pair of scalar and vector
+ */
+export function generateI32VectorBinaryToVectorCases(
+ scalars: readonly number[],
+ vectors: ROArrayArray<number>,
+ op: BinaryOp<number>
+): Case[] {
+ return generateScalarVectorBinaryToVectorCases(scalars, vectors, op, quantizeToI32, i32);
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param vectors array of vector params (2, 3, or 4 elements)
+ * @param scalars array of scalar params
+ * @param op he op to apply to each pair of vector and scalar
+ */
+export function generateVectorI32BinaryToVectorCases(
+ vectors: ROArrayArray<number>,
+ scalars: readonly number[],
+ op: BinaryOp<number>
+): Case[] {
+ return generateVectorScalarBinaryToVectorCases(vectors, scalars, op, quantizeToI32, i32);
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param scalars array of scalar params
+ * @param vectors array of vector params (2, 3, or 4 elements)
+ * @param op he op to apply to each pair of scalar and vector
+ */
+export function generateI64VectorBinaryToVectorCases(
+ scalars: readonly bigint[],
+ vectors: ROArrayArray<bigint>,
+ op: BinaryOp<bigint>
+): Case[] {
+ return generateScalarVectorBinaryToVectorCases(scalars, vectors, op, quantizeToI64, abstractInt);
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param vectors array of vector params (2, 3, or 4 elements)
+ * @param scalars array of scalar params
+ * @param op he op to apply to each pair of vector and scalar
+ */
+export function generateVectorI64BinaryToVectorCases(
+ vectors: ROArrayArray<bigint>,
+ scalars: readonly bigint[],
+ op: BinaryOp<bigint>
+): Case[] {
+ return generateVectorScalarBinaryToVectorCases(vectors, scalars, op, quantizeToI64, abstractInt);
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param param0s array of inputs to try for the first param
+ * @param param1s array of inputs to try for the second param
+ * @param op callback called on each pair of inputs to produce each case
+ * @param quantize function to quantize all values
+ * @param scalarize function to convert numbers to Scalars
+ */
+function generateScalarBinaryToScalarCases<T>(
+ param0s: readonly T[],
+ param1s: readonly T[],
+ op: BinaryOp<T>,
+ quantize: QuantizeFunc<T>,
+ scalarize: ScalarBuilder<T>
+): Case[] {
+ param0s = param0s.map(quantize);
+ param1s = param1s.map(quantize);
+ return cartesianProduct(param0s, param1s).reduce((cases, e) => {
+ const expected = op(e[0], e[1]);
+ if (expected !== undefined) {
+ cases.push({ input: [scalarize(e[0]), scalarize(e[1])], expected: scalarize(expected) });
+ }
+ return cases;
+ }, new Array<Case>());
+}
+
+/**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param param0s array of inputs to try for the first param
+ * @param param1s array of inputs to try for the second param
+ * @param op callback called on each pair of inputs to produce each case
+ */
+export function generateBinaryToI32Cases(
+ param0s: readonly number[],
+ param1s: readonly number[],
+ op: BinaryOp<number>
+) {
+ return generateScalarBinaryToScalarCases(param0s, param1s, op, quantizeToI32, i32);
+}
+
+/**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param param0s array of inputs to try for the first param
+ * @param param1s array of inputs to try for the second param
+ * @param op callback called on each pair of inputs to produce each case
+ */
+export function generateBinaryToU32Cases(
+ param0s: readonly number[],
+ param1s: readonly number[],
+ op: BinaryOp<number>
+) {
+ return generateScalarBinaryToScalarCases(param0s, param1s, op, quantizeToU32, u32);
+}
+
+/**
+ * @returns an array of Cases for operations over a range of inputs
+ * @param param0s array of inputs to try for the first param
+ * @param param1s array of inputs to try for the second param
+ * @param op callback called on each pair of inputs to produce each case
+ */
+export function generateBinaryToI64Cases(
+ param0s: readonly bigint[],
+ param1s: readonly bigint[],
+ op: BinaryOp<bigint>
+) {
+ return generateScalarBinaryToScalarCases(param0s, param1s, op, quantizeToI64, abstractInt);
+}
+
+/**
+ * @returns a Case for the input params with op applied
+ * @param param0 vector param (2, 3, or 4 elements) for the first param
+ * @param param1 vector param (2, 3, or 4 elements) for the second param
+ * @param op the op to apply to each pair of vectors
+ * @param quantize function to quantize all values in vectors and scalars
+ * @param scalarize function to convert numbers to Scalars
+ */
+function makeVectorVectorToScalarCase<T>(
+ param0: readonly T[],
+ param1: readonly T[],
+ op: VectorVectorToScalarOp<T>,
+ quantize: QuantizeFunc<T>,
+ scalarize: ScalarBuilder<T>
+): Case | undefined {
+ const param0_quantized = param0.map(quantize);
+ const param1_quantized = param1.map(quantize);
+ const result = op(param0_quantized, param1_quantized);
+ if (result === undefined) return undefined;
+
+ return {
+ input: [
+ new VectorValue(param0_quantized.map(scalarize)),
+ new VectorValue(param1_quantized.map(scalarize)),
+ ],
+ expected: scalarize(result),
+ };
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param param0s array of vector params (2, 3, or 4 elements) for the first param
+ * @param param1s array of vector params (2, 3, or 4 elements) for the second param
+ * @param op the op to apply to each pair of vectors
+ * @param quantize function to quantize all values in vectors and scalars
+ * @param scalarize function to convert numbers to Scalars
+ */
+function generateVectorVectorToScalarCases<T>(
+ param0s: ROArrayArray<T>,
+ param1s: ROArrayArray<T>,
+ op: VectorVectorToScalarOp<T>,
+ quantize: QuantizeFunc<T>,
+ scalarize: ScalarBuilder<T>
+): Case[] {
+ return param0s.flatMap(param0 => {
+ return param1s
+ .map(param1 => {
+ return makeVectorVectorToScalarCase(param0, param1, op, quantize, scalarize);
+ })
+ .filter(notUndefined);
+ });
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param param0s array of vector params (2, 3, or 4 elements) for the first param
+ * @param param1s array of vector params (2, 3, or 4 elements) for the second param
+ * @param op the op to apply to each pair of vectors
+ */
+export function generateVectorVectorToI32Cases(
+ param0s: ROArrayArray<number>,
+ param1s: ROArrayArray<number>,
+ op: VectorVectorToScalarOp<number>
+): Case[] {
+ return generateVectorVectorToScalarCases(param0s, param1s, op, quantizeToI32, i32);
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param param0s array of vector params (2, 3, or 4 elements) for the first param
+ * @param param1s array of vector params (2, 3, or 4 elements) for the second param
+ * @param op the op to apply to each pair of vectors
+ */
+export function generateVectorVectorToU32Cases(
+ param0s: ROArrayArray<number>,
+ param1s: ROArrayArray<number>,
+ op: VectorVectorToScalarOp<number>
+): Case[] {
+ return generateVectorVectorToScalarCases(param0s, param1s, op, quantizeToU32, u32);
+}
+
+/**
+ * @returns array of Case for the input params with op applied
+ * @param param0s array of vector params (2, 3, or 4 elements) for the first param
+ * @param param1s array of vector params (2, 3, or 4 elements) for the second param
+ * @param op the op to apply to each pair of vectors
+ */
+export function generateVectorVectorToI64Cases(
+ param0s: ROArrayArray<bigint>,
+ param1s: ROArrayArray<bigint>,
+ op: VectorVectorToScalarOp<bigint>
+): Case[] {
+ return generateVectorVectorToScalarCases(param0s, param1s, op, quantizeToI64, abstractInt);
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/case_cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/case_cache.ts
index ff82792d64..345183c5d5 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/case_cache.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/case_cache.ts
@@ -3,21 +3,22 @@ import { unreachable } from '../../../../common/util/util.js';
import BinaryStream from '../../../util/binary_stream.js';
import { deserializeComparator, serializeComparator } from '../../../util/compare.js';
import {
- Scalar,
- Vector,
- serializeValue,
- deserializeValue,
- Matrix,
+ MatrixValue,
Value,
+ VectorValue,
+ deserializeValue,
+ isScalarValue,
+ serializeValue,
} from '../../../util/conversion.js';
import {
- deserializeFPInterval,
FPInterval,
+ deserializeFPInterval,
serializeFPInterval,
} from '../../../util/floating_point.js';
import { flatten2DArray, unflatten2DArray } from '../../../util/math.js';
-import { Case, CaseList, Expectation, isComparator } from './expression.js';
+import { Case } from './case.js';
+import { Expectation, isComparator } from './expectation.js';
enum SerializedExpectationKind {
Value,
@@ -30,7 +31,7 @@ enum SerializedExpectationKind {
/** serializeExpectation() serializes an Expectation to a BinaryStream */
export function serializeExpectation(s: BinaryStream, e: Expectation) {
- if (e instanceof Scalar || e instanceof Vector || e instanceof Matrix) {
+ if (isScalarValue(e) || e instanceof VectorValue || e instanceof MatrixValue) {
s.writeU8(SerializedExpectationKind.Value);
serializeValue(s, e);
return;
@@ -122,27 +123,27 @@ export function deserializeCase(s: BinaryStream): Case {
return { input, expected };
}
-/** CaseListBuilder is a function that builds a CaseList */
-export type CaseListBuilder = () => CaseList;
+/** CaseListBuilder is a function that builds a list of cases, Case[] */
+export type CaseListBuilder = () => Case[];
/**
- * CaseCache is a cache of CaseList.
+ * CaseCache is a cache of Case[].
* CaseCache implements the Cacheable interface, so the cases can be pre-built
* and stored in the data cache, reducing computation costs at CTS runtime.
*/
-export class CaseCache implements Cacheable<Record<string, CaseList>> {
+export class CaseCache implements Cacheable<Record<string, Case[]>> {
/**
* Constructor
* @param name the name of the cache. This must be globally unique.
* @param builders a Record of case-list name to case-list builder.
*/
constructor(name: string, builders: Record<string, CaseListBuilder>) {
- this.path = `webgpu/shader/execution/case-cache/${name}.bin`;
+ this.path = `webgpu/shader/execution/${name}.bin`;
this.builders = builders;
}
/** get() returns the list of cases with the given name */
- public async get(name: string): Promise<CaseList> {
+ public async get(name: string): Promise<Case[]> {
const data = await dataCache.fetch(this);
return data[name];
}
@@ -151,8 +152,8 @@ export class CaseCache implements Cacheable<Record<string, CaseList>> {
* build() implements the Cacheable.build interface.
* @returns the data.
*/
- build(): Promise<Record<string, CaseList>> {
- const built: Record<string, CaseList> = {};
+ build(): Promise<Record<string, Case[]>> {
+ const built: Record<string, Case[]> = {};
for (const name in this.builders) {
const cases = this.builders[name]();
built[name] = cases;
@@ -164,7 +165,7 @@ export class CaseCache implements Cacheable<Record<string, CaseList>> {
* serialize() implements the Cacheable.serialize interface.
* @returns the serialized data.
*/
- serialize(data: Record<string, CaseList>): Uint8Array {
+ serialize(data: Record<string, Case[]>): Uint8Array {
const maxSize = 32 << 20; // 32MB - max size for a file
const stream = new BinaryStream(new ArrayBuffer(maxSize));
stream.writeU32(Object.keys(data).length);
@@ -179,9 +180,9 @@ export class CaseCache implements Cacheable<Record<string, CaseList>> {
* deserialize() implements the Cacheable.deserialize interface.
* @returns the deserialize data.
*/
- deserialize(array: Uint8Array): Record<string, CaseList> {
+ deserialize(array: Uint8Array): Record<string, Case[]> {
const s = new BinaryStream(array.buffer);
- const casesByName: Record<string, CaseList> = {};
+ const casesByName: Record<string, Case[]> = {};
const numRecords = s.readU32();
for (let i = 0; i < numRecords; i++) {
const name = s.readString();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/constructor/non_zero.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/constructor/non_zero.spec.ts
new file mode 100644
index 0000000000..c28d3a8edb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/constructor/non_zero.spec.ts
@@ -0,0 +1,797 @@
+export const description = `
+Execution Tests for value constructors from components
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import {
+ ArrayValue,
+ MatrixType,
+ ScalarKind,
+ Type,
+ Value,
+ VectorType,
+ VectorValue,
+ scalarTypeOf,
+ vec2,
+ vec3,
+} from '../../../../util/conversion.js';
+import { FP } from '../../../../util/floating_point.js';
+import { allInputSources, basicExpressionBuilder, run } from '../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+/** @returns true if 'v' is 'min' or 'max' */
+function isMinOrMax(v: number | 'min' | 'max') {
+ return v === 'min' || v === 'max';
+}
+
+/** A list of concrete types to test for the given abstract-numeric type */
+const kConcreteTypesForAbstractType = {
+ 'abstract-float': ['f32', 'f16'] as const,
+ 'abstract-int': ['f32', 'f16', 'i32', 'u32'] as const,
+ 'vec3<abstract-int>': ['vec3f', 'vec3h', 'vec3i', 'vec3u'] as const,
+ 'vec4<abstract-float>': ['vec4f', 'vec4h'] as const,
+ 'mat2x3<abstract-float>': ['mat2x3f', 'mat2x3h'] as const,
+};
+
+/**
+ * @returns the lowest finite value for 'kind' if 'v' is 'min',
+ * the highest finite value for 'kind' if 'v' is 'max',
+ * otherwise returns 'v'
+ */
+function valueFor(v: number | 'min' | 'max', kind: 'bool' | 'i32' | 'u32' | 'f32' | 'f16') {
+ if (!isMinOrMax(v)) {
+ return v as number;
+ }
+ switch (kind) {
+ case 'bool':
+ return v === 'min' ? 0 : 1;
+ case 'i32':
+ return v === 'min' ? -0x80000000 : 0x7fffffff;
+ case 'u32':
+ return v === 'min' ? 0 : 0xffffffff;
+ case 'f32':
+ return v === 'min' ? FP['f32'].constants().negative.min : FP['f32'].constants().positive.max;
+ case 'f16':
+ return v === 'min' ? FP['f16'].constants().negative.min : FP['f16'].constants().positive.max;
+ }
+}
+
+g.test('scalar_identity')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a scalar constructed from a value of the same type produces the expected value`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('type', ['bool', 'i32', 'u32', 'f32', 'f16'] as const)
+ .combine('value', ['min', 'max', 1, 2, 5, 100] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ t.skipIf(t.params.type === 'bool' && !isMinOrMax(t.params.value));
+ })
+ .fn(async t => {
+ const type = Type[t.params.type];
+ const value = valueFor(t.params.value, t.params.type);
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${type}(${ops[0]})`),
+ [type],
+ type,
+ t.params,
+ [{ input: [type.create(value)], expected: type.create(value) }]
+ );
+ });
+
+g.test('vector_identity')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a vector constructed from a value of the same type produces the expected value`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('type', ['bool', 'i32', 'u32', 'f32', 'f16'] as const)
+ .combine('width', [2, 3, 4] as const)
+ .combine('infer_type', [false, true] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.type];
+ const vectorType = Type.vec(t.params.width, elementType);
+ const elements: number[] = [];
+ const fn = t.params.infer_type ? `vec${t.params.width}` : `${vectorType}`;
+ for (let i = 0; i < t.params.width; i++) {
+ if (t.params.type === 'bool') {
+ elements.push(i & 1);
+ } else {
+ elements.push((i + 1) * 10);
+ }
+ }
+
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${fn}(${ops[0]})`),
+ [vectorType],
+ vectorType,
+ t.params,
+ [
+ {
+ input: vectorType.create(elements),
+ expected: vectorType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('concrete_vector_splat')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a vector constructed from a single concrete scalar produces the expected value`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('type', ['bool', 'i32', 'u32', 'f32', 'f16'] as const)
+ .combine('value', ['min', 'max', 1, 2, 5, 100] as const)
+ .combine('width', [2, 3, 4] as const)
+ .combine('infer_type', [false, true] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ t.skipIf(t.params.type === 'bool' && !isMinOrMax(t.params.value));
+ })
+ .fn(async t => {
+ const value = valueFor(t.params.value, t.params.type);
+ const elementType = Type[t.params.type];
+ const vectorType = Type.vec(t.params.width, elementType);
+ const fn = t.params.infer_type ? `vec${t.params.width}` : `${vectorType}`;
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${fn}(${ops[0]})`),
+ [elementType],
+ vectorType,
+ t.params,
+ [{ input: [elementType.create(value)], expected: vectorType.create(value) }]
+ );
+ });
+
+g.test('abstract_vector_splat')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a vector constructed from a single abstract scalar produces the expected value`)
+ .params(u =>
+ u
+ .combine('abstract_type', ['abstract-int', 'abstract-float'] as const)
+ .expand('concrete_type', t => kConcreteTypesForAbstractType[t.abstract_type])
+ .combine('value', [1, 2, 5, 100] as const)
+ .combine('width', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.concrete_type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const suffix = t.params.abstract_type === 'abstract-float' ? '.0' : '';
+ const concreteElementType = Type[t.params.concrete_type];
+ const concreteVectorType = Type.vec(t.params.width, concreteElementType);
+ const fn = `vec${t.params.width}`;
+ await run(
+ t,
+ basicExpressionBuilder(_ => `${fn}(${t.params.value * 0x100000000}${suffix}) / 0x100000000`),
+ [],
+ concreteVectorType,
+ { inputSource: 'const' },
+ [{ input: [], expected: concreteVectorType.create(t.params.value) }]
+ );
+ });
+
+g.test('concrete_vector_elements')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a vector constructed from concrete element values produces the expected value`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('type', ['bool', 'i32', 'u32', 'f32', 'f16'] as const)
+ .combine('width', [2, 3, 4] as const)
+ .combine('infer_type', [false, true] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.type];
+ const vectorType = Type.vec(t.params.width, elementType);
+ const elements: number[] = [];
+ const fn = t.params.infer_type ? `vec${t.params.width}` : `${vectorType}`;
+ for (let i = 0; i < t.params.width; i++) {
+ if (t.params.type === 'bool') {
+ elements.push(i & 1);
+ } else {
+ elements.push((i + 1) * 10);
+ }
+ }
+
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${fn}(${ops.join(', ')})`),
+ elements.map(e => elementType),
+ vectorType,
+ t.params,
+ [
+ {
+ input: elements.map(v => elementType.create(v)),
+ expected: vectorType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('abstract_vector_elements')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a vector constructed from abstract element values produces the expected value`)
+ .params(u =>
+ u
+ .combine('abstract_type', ['abstract-int', 'abstract-float'] as const)
+ .expand('concrete_type', t => kConcreteTypesForAbstractType[t.abstract_type])
+ .combine('width', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.concrete_type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const suffix = t.params.abstract_type === 'abstract-float' ? '.0' : '';
+ const concreteElementType = Type[t.params.concrete_type];
+ const concreteVectorType = Type.vec(t.params.width, concreteElementType);
+ const fn = `vec${t.params.width}`;
+ const elements: number[] = [];
+ for (let i = 0; i < t.params.width; i++) {
+ elements.push((i + 1) * 10);
+ }
+ await run(
+ t,
+ basicExpressionBuilder(
+ _ => `${fn}(${elements.map(v => `${v * 0x100000000}${suffix}`).join(', ')}) / 0x100000000`
+ ),
+ [],
+ concreteVectorType,
+ { inputSource: 'const' },
+ [{ input: [], expected: concreteVectorType.create(elements) }]
+ );
+ });
+
+const kMixSignatures = [
+ '2s', // [vec2, scalar]
+ 's2', // [scalar, vec2]
+ '2ss', // [vec2, scalar, scalar]
+ 's2s', // [scalar, vec2, scalar]
+ 'ss2', // [scalar, scalar, vec2 ]
+ '22', // [vec2, vec2]
+ '3s', // [vec3, scalar]
+ 's3', // [scalar, vec3]
+] as const;
+
+g.test('concrete_vector_mix')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(
+ `Test that a vector constructed from a mix of concrete element values and sub-vectors produces the expected value`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('type', ['bool', 'i32', 'u32', 'f32', 'f16'] as const)
+ .combine('signature', kMixSignatures)
+ .combine('infer_type', [false, true] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.type];
+ let width = 0;
+ const elementValue = (i: number) => (t.params.type === 'bool' ? i & 1 : (i + 1) * 10);
+ const elements: number[] = [];
+ const nextValue = () => {
+ const value = elementValue(width++);
+ elements.push(value);
+ return elementType.create(value);
+ };
+ const args: Value[] = [];
+ for (const c of t.params.signature) {
+ switch (c) {
+ case '2':
+ args.push(vec2(nextValue(), nextValue()));
+ break;
+ case '3':
+ args.push(vec3(nextValue(), nextValue(), nextValue()));
+ break;
+ case 's':
+ args.push(nextValue());
+ break;
+ }
+ }
+ const vectorType = Type.vec(width, elementType);
+ const fn = t.params.infer_type ? `vec${width}` : `${vectorType}`;
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${fn}(${ops.join(', ')})`),
+ args.map(e => e.type),
+ vectorType,
+ t.params,
+ [
+ {
+ input: args,
+ expected: vectorType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('abstract_vector_mix')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(
+ `Test that a vector constructed from a mix of abstract element values and sub-vectors produces the expected value`
+ )
+ .params(u =>
+ u
+ .combine('abstract_type', ['abstract-int', 'abstract-float'] as const)
+ .expand('concrete_type', t => kConcreteTypesForAbstractType[t.abstract_type])
+ .combine('signature', kMixSignatures)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.concrete_type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ let width = 0;
+ const suffix = t.params.abstract_type === 'abstract-float' ? '.0' : '';
+ const concreteElementType = Type[t.params.concrete_type];
+ const elementValue = (i: number) => (i + 1) * 10;
+ const elements: number[] = [];
+ const nextValue = () => {
+ const value = elementValue(width++);
+ elements.push(value);
+ return `${value * 0x100000000}${suffix}`;
+ };
+ const args: string[] = [];
+ for (const c of t.params.signature) {
+ switch (c) {
+ case '2':
+ args.push(`vec2(${nextValue()}, ${nextValue()})`);
+ break;
+ case '3':
+ args.push(`vec3(${nextValue()}, ${nextValue()}, ${nextValue()})`);
+ break;
+ case 's':
+ args.push(`${nextValue()}`);
+ break;
+ }
+ }
+ const concreteVectorType = Type.vec(width, concreteElementType);
+ const fn = `vec${width}`;
+ await run(
+ t,
+ basicExpressionBuilder(_ => `${fn}(${args.join(', ')}) / 0x100000000`),
+ [],
+ concreteVectorType,
+ { inputSource: 'const' },
+ [
+ {
+ input: [],
+ expected: concreteVectorType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('matrix_identity')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a matrix constructed from a value of the same type produces the expected value`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('type', ['f32', 'f16'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ .combine('infer_type', [false, true] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.type];
+ const matrixType = Type.mat(t.params.columns, t.params.rows, elementType);
+ const elements: number[] = [];
+ for (let column = 0; column < t.params.columns; column++) {
+ for (let row = 0; row < t.params.rows; row++) {
+ elements.push((column + 1) * 10 + (row + 1));
+ }
+ }
+ const fn = t.params.infer_type ? `mat${t.params.columns}x${t.params.rows}` : `${matrixType}`;
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${fn}(${ops[0]})`),
+ [matrixType],
+ matrixType,
+ t.params,
+ [
+ {
+ input: matrixType.create(elements),
+ expected: matrixType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('concrete_matrix_elements')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a matrix constructed from concrete element values produces the expected value`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('type', ['f32', 'f16'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ .combine('infer_type', [false, true] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.type];
+ const matrixType = Type.mat(t.params.columns, t.params.rows, elementType);
+ const elements: number[] = [];
+ for (let column = 0; column < t.params.columns; column++) {
+ for (let row = 0; row < t.params.rows; row++) {
+ elements.push((column + 1) * 10 + (row + 1));
+ }
+ }
+ const fn = t.params.infer_type ? `mat${t.params.columns}x${t.params.rows}` : `${matrixType}`;
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${fn}(${ops.join(', ')})`),
+ elements.map(e => elementType),
+ matrixType,
+ t.params,
+ [
+ {
+ input: elements.map(e => elementType.create(e)),
+ expected: matrixType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('abstract_matrix_elements')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a matrix constructed from concrete element values produces the expected value`)
+ .params(u =>
+ u
+ .combine('concrete_type', ['f32', 'f16'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.concrete_type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const concreteElementType = Type[t.params.concrete_type];
+ const concreteMatrixType = Type.mat(t.params.columns, t.params.rows, concreteElementType);
+ const elements: number[] = [];
+ for (let column = 0; column < t.params.columns; column++) {
+ for (let row = 0; row < t.params.rows; row++) {
+ elements.push((column + 1) * 10 + (row + 1));
+ }
+ }
+ const fn = `mat${t.params.columns}x${t.params.rows}`;
+ await run(
+ t,
+ basicExpressionBuilder(
+ _ => `${fn}(${elements.map(v => `${v * 0x100000000}.0`).join(', ')}) * (1.0 / 0x100000000)`
+ ),
+ [],
+ concreteMatrixType,
+ { inputSource: 'const' },
+ [
+ {
+ input: [],
+ expected: concreteMatrixType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('concrete_matrix_column_vectors')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a matrix constructed from concrete column vectors produces the expected value`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('type', ['f32', 'f16'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ .combine('infer_type', [false, true] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.type];
+ const columnType = Type.vec(t.params.rows, elementType);
+ const matrixType = Type.mat(t.params.columns, t.params.rows, elementType);
+ const elements: number[] = [];
+ const columnVectors: VectorValue[] = [];
+ for (let column = 0; column < t.params.columns; column++) {
+ const columnElements: number[] = [];
+ for (let row = 0; row < t.params.rows; row++) {
+ const v = (column + 1) * 10 + (row + 1);
+ elements.push(v);
+ columnElements.push(v);
+ }
+ columnVectors.push(columnType.create(columnElements));
+ }
+ const fn = t.params.infer_type ? `mat${t.params.columns}x${t.params.rows}` : `${matrixType}`;
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${fn}(${ops.join(', ')})`),
+ columnVectors.map(v => v.type),
+ matrixType,
+ t.params,
+ [
+ {
+ input: columnVectors,
+ expected: matrixType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('abstract_matrix_column_vectors')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that a matrix constructed from abstract column vectors produces the expected value`)
+ .params(u =>
+ u
+ .combine('concrete_type', ['f32', 'f16'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.concrete_type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const concreteElementType = Type[t.params.concrete_type];
+ const concreteMatrixType = Type.mat(t.params.columns, t.params.rows, concreteElementType);
+ const elements: number[] = [];
+ const columnVectors: string[] = [];
+ for (let column = 0; column < t.params.columns; column++) {
+ const columnElements: string[] = [];
+ for (let row = 0; row < t.params.rows; row++) {
+ const v = (column + 1) * 10 + (row + 1);
+ elements.push(v);
+ columnElements.push(`${v * 0x100000000}`);
+ }
+ columnVectors.push(`vec${t.params.rows}(${columnElements.join(', ')})`);
+ }
+ const fn = `mat${t.params.columns}x${t.params.rows}`;
+ await run(
+ t,
+ basicExpressionBuilder(_ => `${fn}(${columnVectors.join(', ')}) * (1.0 / 0x100000000)`),
+ [],
+ concreteMatrixType,
+ { inputSource: 'const' },
+ [
+ {
+ input: [],
+ expected: concreteMatrixType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('concrete_array_elements')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that an array constructed from concrete element values produces the expected value`)
+ .params(u =>
+ u
+ .combine('inputSource', allInputSources)
+ .combine('type', ['bool', 'i32', 'u32', 'f32', 'f16', 'vec3f', 'vec4i'] as const)
+ .combine('length', [1, 5, 10] as const)
+ .combine('infer_type', [false, true] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const elementType = Type[t.params.type];
+ const arrayType = Type.array(t.params.length, elementType);
+ const elements: number[] = [];
+ for (let i = 0; i < t.params.length; i++) {
+ elements.push((i + 1) * 10);
+ }
+ const fn = t.params.infer_type ? `array` : `${arrayType}`;
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${fn}(${ops.join(', ')})`),
+ elements.map(e => elementType),
+ arrayType,
+ t.params,
+ [
+ {
+ input: elements.map(e => elementType.create(e)),
+ expected: arrayType.create(elements),
+ },
+ ]
+ );
+ });
+
+g.test('abstract_array_elements')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that an array constructed from element values produces the expected value`)
+ .params(u =>
+ u
+ .combine('abstract_type', [
+ 'abstract-int',
+ 'abstract-float',
+ 'vec3<abstract-int>',
+ 'vec4<abstract-float>',
+ 'mat2x3<abstract-float>',
+ ] as const)
+ .expand('concrete_type', t => kConcreteTypesForAbstractType[t.abstract_type])
+ .combine('length', [1, 5, 10] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(Type[t.params.concrete_type]).kind === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const count = t.params.length;
+ const concreteElementType = Type[t.params.concrete_type];
+ const concreteArrayType = Type.array(count, concreteElementType);
+ const elements: { args: string; value: Value }[] = [];
+ let i = 0;
+ const nextValue = () => ++i * 10;
+ for (let i = 0; i < count; i++) {
+ switch (t.params.abstract_type) {
+ case 'abstract-int': {
+ const value = nextValue();
+ elements.push({ args: `${value}`, value: concreteElementType.create(value) });
+ break;
+ }
+ case 'abstract-float': {
+ const value = nextValue();
+ elements.push({ args: `${value}.0`, value: concreteElementType.create(value) });
+ break;
+ }
+ case 'vec3<abstract-int>': {
+ const x = nextValue();
+ const y = nextValue();
+ const z = nextValue();
+ elements.push({
+ args: `vec3(${x}, ${y}, ${z})`,
+ value: (concreteElementType as VectorType).create([x, y, z]),
+ });
+ break;
+ }
+ case 'vec4<abstract-float>': {
+ const x = nextValue();
+ const y = nextValue();
+ const z = nextValue();
+ const w = nextValue();
+ elements.push({
+ args: `vec4(${x}.0, ${y}.0, ${z}.0, ${w}.0)`,
+ value: (concreteElementType as VectorType).create([x, y, z, w]),
+ });
+ break;
+ }
+ case 'mat2x3<abstract-float>': {
+ const e00 = nextValue();
+ const e01 = nextValue();
+ const e02 = nextValue();
+ const e10 = nextValue();
+ const e11 = nextValue();
+ const e12 = nextValue();
+ elements.push({
+ args: `mat2x3(vec3(${e00}.0, ${e01}.0, ${e02}.0), vec3(${e10}.0, ${e11}.0, ${e12}.0))`,
+ value: (concreteElementType as MatrixType).create([e00, e01, e02, e10, e11, e12]),
+ });
+ break;
+ }
+ }
+ }
+ const fn = `array`;
+ await run(
+ t,
+ basicExpressionBuilder(_ => `${fn}(${elements.map(e => e.args).join(', ')})`),
+ [],
+ concreteArrayType,
+ { inputSource: 'const' },
+ [
+ {
+ input: [],
+ expected: new ArrayValue(elements.map(e => e.value)),
+ },
+ ]
+ );
+ });
+
+g.test('structure')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(`Test that an structure constructed from element values produces the expected value`)
+ .params(u =>
+ u
+ .combine('member_types', [
+ ['bool'],
+ ['u32'],
+ ['vec3f'],
+ ['i32', 'u32'],
+ ['i32', 'f16', 'vec4i', 'mat3x2f'],
+ ['bool', 'u32', 'f16', 'vec3f', 'vec2i'],
+ ['i32', 'u32', 'f32', 'f16', 'vec3f', 'vec4i'],
+ ] as readonly ScalarKind[][])
+ .combine('nested', [false, true])
+ .beginSubcases()
+ .expand('member_index', t => t.member_types.map((_, i) => i))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.member_types.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const memberType = Type[t.params.member_types[t.params.member_index]];
+ const values = t.params.member_types.map((ty, i) => Type[ty].create(i));
+
+ const builder = basicExpressionBuilder(ops =>
+ t.params.nested
+ ? `OuterStruct(10, MyStruct(${ops.join(', ')}), 20).inner.member_${t.params.member_index}`
+ : `MyStruct(${ops.join(', ')}).member_${t.params.member_index}`
+ );
+ await run(
+ t,
+ (parameterTypes, resultType, cases, inputSource) => {
+ return `
+${t.params.member_types.includes('f16') ? 'enable f16;' : ''}
+
+${builder(parameterTypes, resultType, cases, inputSource)}
+
+struct MyStruct {
+${t.params.member_types.map((ty, i) => ` member_${i} : ${ty},`).join('\n')}
+};
+struct OuterStruct {
+ pre : i32,
+ inner : MyStruct,
+ post : i32,
+};
+`;
+ },
+ t.params.member_types.map(ty => Type[ty]),
+ memberType,
+ { inputSource: 'const' },
+ [{ input: values, expected: values[t.params.member_index] }]
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/constructor/zero_value.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/constructor/zero_value.spec.ts
new file mode 100644
index 0000000000..5899021550
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/constructor/zero_value.spec.ts
@@ -0,0 +1,162 @@
+export const description = `
+Execution Tests for zero value constructors
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { ScalarKind, Type } from '../../../../util/conversion.js';
+import { basicExpressionBuilder, run } from '../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('scalar')
+ .specURL('https://www.w3.org/TR/WGSL/#zero-value-builtin-function')
+ .desc(`Test that a zero value scalar constructor produces the expected zero value`)
+ .params(u => u.combine('type', ['bool', 'i32', 'u32', 'f32', 'f16'] as const))
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const type = Type[t.params.type];
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${type}()`),
+ [],
+ type,
+ { inputSource: 'const' },
+ [{ input: [], expected: type.create(0) }]
+ );
+ });
+
+g.test('vector')
+ .specURL('https://www.w3.org/TR/WGSL/#zero-value-builtin-function')
+ .desc(`Test that a zero value vector constructor produces the expected zero value`)
+ .params(u =>
+ u
+ .combine('type', ['bool', 'i32', 'u32', 'f32', 'f16'] as const)
+ .combine('width', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const type = Type.vec(t.params.width, Type[t.params.type]);
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${type}()`),
+ [],
+ type,
+ { inputSource: 'const' },
+ [{ input: [], expected: type.create(0) }]
+ );
+ });
+
+g.test('matrix')
+ .specURL('https://www.w3.org/TR/WGSL/#zero-value-builtin-function')
+ .desc(`Test that a zero value matrix constructor produces the expected zero value`)
+ .params(u =>
+ u
+ .combine('type', ['f32', 'f16'] as const)
+ .combine('columns', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const type = Type.mat(t.params.columns, t.params.rows, Type[t.params.type]);
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${type}()`),
+ [],
+ type,
+ { inputSource: 'const' },
+ [{ input: [], expected: type.create(0) }]
+ );
+ });
+
+g.test('array')
+ .specURL('https://www.w3.org/TR/WGSL/#zero-value-builtin-function')
+ .desc(`Test that a zero value matrix constructor produces the expected zero value`)
+ .params(u =>
+ u
+ .combine('type', ['bool', 'i32', 'u32', 'f32', 'f16', 'vec3f', 'vec4i'] as const)
+ .combine('length', [1, 5, 10] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const type = Type.array(t.params.length, Type[t.params.type]);
+ await run(
+ t,
+ basicExpressionBuilder(ops => `${type}()`),
+ [],
+ type,
+ { inputSource: 'const' },
+ [{ input: [], expected: type.create(0) }]
+ );
+ });
+
+g.test('structure')
+ .specURL('https://www.w3.org/TR/WGSL/#zero-value-builtin-function')
+ .desc(`Test that an structure constructed from element values produces the expected value`)
+ .params(u =>
+ u
+ .combine('member_types', [
+ ['bool'],
+ ['u32'],
+ ['vec3f'],
+ ['i32', 'u32'],
+ ['i32', 'f16', 'vec4i', 'mat3x2f'],
+ ['bool', 'u32', 'f16', 'vec3f', 'vec2i'],
+ ['i32', 'u32', 'f32', 'f16', 'vec3f', 'vec4i'],
+ ] as readonly ScalarKind[][])
+ .combine('nested', [false, true])
+ .beginSubcases()
+ .expand('member_index', t => t.member_types.map((_, i) => i))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.member_types.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(async t => {
+ const memberType = Type[t.params.member_types[t.params.member_index]];
+ const builder = basicExpressionBuilder(_ =>
+ t.params.nested
+ ? `OuterStruct().inner.member_${t.params.member_index}`
+ : `MyStruct().member_${t.params.member_index}`
+ );
+ await run(
+ t,
+ (parameterTypes, resultType, cases, inputSource) => {
+ return `
+${t.params.member_types.includes('f16') ? 'enable f16;' : ''}
+
+${builder(parameterTypes, resultType, cases, inputSource)}
+
+struct MyStruct {
+${t.params.member_types.map((ty, i) => ` member_${i} : ${ty},`).join('\n')}
+};
+struct OuterStruct {
+ pre : i32,
+ inner : MyStruct,
+ post : i32,
+};
+`;
+ },
+ [],
+ memberType,
+ { inputSource: 'const' },
+ [{ input: [], expected: memberType.create(0) }]
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/expectation.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/expectation.ts
new file mode 100644
index 0000000000..955fa68138
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/expectation.ts
@@ -0,0 +1,38 @@
+import { ROArrayArray } from '../../../../common/util/types.js';
+import { Comparator, compare } from '../../../util/compare.js';
+import {
+ ArrayValue,
+ MatrixValue,
+ Value,
+ VectorValue,
+ isScalarValue,
+} from '../../../util/conversion.js';
+import { FPInterval } from '../../../util/floating_point.js';
+
+export type Expectation =
+ | Value
+ | FPInterval
+ | readonly FPInterval[]
+ | ROArrayArray<FPInterval>
+ | Comparator;
+
+/** @returns if this Expectation actually a Comparator */
+export function isComparator(e: Expectation): e is Comparator {
+ return !(
+ e instanceof FPInterval ||
+ isScalarValue(e) ||
+ e instanceof VectorValue ||
+ e instanceof MatrixValue ||
+ e instanceof ArrayValue ||
+ e instanceof Array
+ );
+}
+
+/** @returns the input if it is already a Comparator, otherwise wraps it in a 'value' comparator */
+export function toComparator(input: Expectation): Comparator {
+ if (isComparator(input)) {
+ return input;
+ }
+
+ return { compare: got => compare(got, input as Value), kind: 'value' };
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/expression.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/expression.ts
index f85516f29b..be8f1fd7fd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/expression.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/expression.ts
@@ -1,70 +1,25 @@
import { globalTestConfig } from '../../../../common/framework/test_config.js';
-import { ROArrayArray } from '../../../../common/util/types.js';
import { assert, objectEquals, unreachable } from '../../../../common/util/util.js';
import { GPUTest } from '../../../gpu_test.js';
-import { compare, Comparator, ComparatorImpl } from '../../../util/compare.js';
+import { Comparator, ComparatorImpl } from '../../../util/compare.js';
import { kValue } from '../../../util/constants.js';
import {
+ MatrixType,
+ ScalarValue,
ScalarType,
- Scalar,
Type,
- TypeVec,
- TypeU32,
- Value,
- Vector,
VectorType,
- u32,
- i32,
- Matrix,
- MatrixType,
- ScalarBuilder,
+ Value,
+ VectorValue,
+ isAbstractType,
scalarTypeOf,
+ ArrayType,
+ elementTypeOf,
} from '../../../util/conversion.js';
-import { FPInterval } from '../../../util/floating_point.js';
-import {
- cartesianProduct,
- QuantizeFunc,
- quantizeToI32,
- quantizeToU32,
-} from '../../../util/math.js';
-
-export type Expectation =
- | Value
- | FPInterval
- | readonly FPInterval[]
- | ROArrayArray<FPInterval>
- | Comparator;
-
-/** @returns if this Expectation actually a Comparator */
-export function isComparator(e: Expectation): e is Comparator {
- return !(
- e instanceof FPInterval ||
- e instanceof Scalar ||
- e instanceof Vector ||
- e instanceof Matrix ||
- e instanceof Array
- );
-}
-
-/** @returns the input if it is already a Comparator, otherwise wraps it in a 'value' comparator */
-export function toComparator(input: Expectation): Comparator {
- if (isComparator(input)) {
- return input;
- }
-
- return { compare: got => compare(got, input as Value), kind: 'value' };
-}
-
-/** Case is a single expression test case. */
-export type Case = {
- // The input value(s)
- input: Value | ReadonlyArray<Value>;
- // The expected result, or function to check the result
- expected: Expectation;
-};
+import { align } from '../../../util/math.js';
-/** CaseList is a list of Cases */
-export type CaseList = Array<Case>;
+import { Case } from './case.js';
+import { toComparator } from './expectation.js';
/** The input value source */
export type InputSource =
@@ -79,6 +34,9 @@ export const allInputSources: InputSource[] = ['const', 'uniform', 'storage_r',
/** Just constant input source */
export const onlyConstInputSource: InputSource[] = ['const'];
+/** All input sources except const */
+export const allButConstInputSource: InputSource[] = ['uniform', 'storage_r', 'storage_rw'];
+
/** Configuration for running a expression test */
export type Config = {
// Where the input values are read from
@@ -92,127 +50,157 @@ export type Config = {
vectorize?: number;
};
-// 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;
- }
- 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;
- }
+/**
+ * @returns the size and alignment in bytes of the type 'ty', taking into
+ * consideration storage alignment constraints and abstract numerics, which are
+ * encoded as a struct of holding two u32s.
+ */
+function sizeAndAlignmentOf(ty: Type, source: InputSource): { size: number; alignment: number } {
+ if (ty instanceof ScalarType) {
+ if (ty.kind === 'abstract-float' || ty.kind === 'abstract-int') {
+ // AbstractFloats and AbstractInts are passed out of the shader via structs of
+ // 2x u32s and unpacking containers as arrays
+ return { size: 8, alignment: 8 };
}
- unreachable(`AbstractFloats have not yet been implemented for ${ty.toString()}`);
+ return { size: ty.size, alignment: ty.alignment };
+ }
+
+ if (ty instanceof VectorType) {
+ const out = sizeAndAlignmentOf(ty.elementType, source);
+ const n = ty.width === 3 ? 4 : ty.width;
+ out.size *= n;
+ out.alignment *= n;
+ return out;
}
if (ty instanceof MatrixType) {
- switch (ty.cols) {
- case 2:
- switch (ty.rows) {
- case 2:
- return 16;
- case 3:
- return 32;
- case 4:
- return 32;
- }
- break;
- case 3:
- switch (ty.rows) {
- case 2:
- return 32;
- case 3:
- return 64;
- case 4:
- return 64;
- }
- break;
- case 4:
- switch (ty.rows) {
- case 2:
- return 32;
- case 3:
- return 64;
- case 4:
- return 64;
- }
- break;
+ const out = sizeAndAlignmentOf(ty.elementType, source);
+ const n = ty.rows === 3 ? 4 : ty.rows;
+ out.size *= n * ty.cols;
+ out.alignment *= n;
+ return out;
+ }
+
+ if (ty instanceof ArrayType) {
+ const out = sizeAndAlignmentOf(ty.elementType, source);
+ if (source === 'uniform') {
+ out.alignment = align(out.alignment, 16);
}
- unreachable(
- `Attempted to get stride length for a matrix with dimensions (${ty.cols}x${ty.rows}), which isn't currently handled`
- );
+ out.size *= ty.count;
+ return out;
+ }
+
+ unreachable(`unhandled type: ${ty}`);
+}
+
+/**
+ * @returns the stride in bytes of the type 'ty', taking into consideration abstract numerics,
+ * which are encoded as a struct of 2 x u32.
+ */
+function strideOf(ty: Type, source: InputSource): number {
+ const sizeAndAlign = sizeAndAlignmentOf(ty, source);
+ return align(sizeAndAlign.size, sizeAndAlign.alignment);
+}
+
+/**
+ * Calls 'callback' with the layout information of each structure member with the types 'members'.
+ * @returns the byte size, stride and alignment of the structure.
+ */
+export function structLayout(
+ members: Type[],
+ source: InputSource,
+ callback?: (m: {
+ index: number;
+ type: Type;
+ size: number;
+ alignment: number;
+ offset: number;
+ }) => void
+): { size: number; stride: number; alignment: number } {
+ let offset = 0;
+ let alignment = 1;
+ for (let i = 0; i < members.length; i++) {
+ const member = members[i];
+ const sizeAndAlign = sizeAndAlignmentOf(member, source);
+ offset = align(offset, sizeAndAlign.alignment);
+ if (callback) {
+ callback({
+ index: i,
+ type: member,
+ size: sizeAndAlign.size,
+ alignment: sizeAndAlign.alignment,
+ offset,
+ });
+ }
+ offset += sizeAndAlign.size;
+ alignment = Math.max(alignment, sizeAndAlign.alignment);
}
- // Handles scalars and vectors
- return 16;
+ if (source === 'uniform') {
+ alignment = align(alignment, 16);
+ }
+
+ const size = offset;
+ const stride = align(size, alignment);
+ return { size, stride, alignment };
+}
+
+/** @returns the stride in bytes between two consecutive structures with the given members */
+export function structStride(members: Type[], source: InputSource): number {
+ return structLayout(members, source).stride;
}
-// Helper for summing up all of the stride values for an array of Types
-function valueStrides(tys: Type[]): number {
- return tys.map(valueStride).reduce((sum, c) => sum + c);
+/** @returns the WGSL to describe the structure members in 'members' */
+function wgslMembers(members: Type[], source: InputSource, memberName: (i: number) => string) {
+ const lines: string[] = [];
+ const layout = structLayout(members, source, m => {
+ lines.push(` @size(${m.size}) ${memberName(lines.length)} : ${m.type},`);
+ });
+ const padding = layout.stride - layout.size;
+ if (padding > 0) {
+ // Pad with a 'f16' if the padding requires an odd multiple of 2 bytes.
+ // This is required as 'i32' has an alignment and size of 4 bytes.
+ const ty = (padding & 2) !== 0 ? 'f16' : 'i32';
+ lines.push(` @size(${padding}) padding : ${ty},`);
+ }
+ return lines.join('\n');
}
// 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-int', `Custom handling is implemented for 'abstract-int' values`);
assert(
ty.kind !== 'abstract-float',
`Custom handling is implemented for 'abstract-float' values`
);
if (ty.kind === 'bool') {
- return TypeU32;
+ return Type.u32;
}
}
if (ty instanceof VectorType) {
- return TypeVec(ty.width, storageType(ty.elementType) as ScalarType);
+ return Type.vec(ty.width, storageType(ty.elementType) as ScalarType);
+ }
+ if (ty instanceof ArrayType) {
+ return Type.array(ty.count, storageType(ty.elementType));
}
return ty;
}
+/** Structure used to hold [from|to]Storage conversion helpers */
+type TypeConversionHelpers = {
+ // The module-scope WGSL to emit with the shader.
+ wgsl: string;
+ // A function that generates a unique WGSL identifier.
+ uniqueID: () => string;
+};
+
// Helper for converting a value of the type 'ty' from the storage type.
-function fromStorage(ty: Type, expr: string): string {
+function fromStorage(ty: Type, expr: string, helpers: TypeConversionHelpers): string {
if (ty instanceof ScalarType) {
- assert(ty.kind !== 'abstract-float', `AbstractFloat values should not be in input storage`);
+ assert(ty.kind !== 'abstract-int', `'abstract-int' values should not be in input storage`);
+ assert(ty.kind !== 'abstract-float', `'abstract-float' values should not be in input storage`);
assert(ty.kind !== 'f64', `'No storage type defined for 'f64' values`);
if (ty.kind === 'bool') {
return `${expr} != 0u`;
@@ -220,23 +208,46 @@ function fromStorage(ty: Type, expr: string): string {
}
if (ty instanceof VectorType) {
assert(
+ ty.elementType.kind !== 'abstract-int',
+ `'abstract-int' values cannot appear in input storage`
+ );
+ assert(
ty.elementType.kind !== 'abstract-float',
- `AbstractFloat values cannot appear in input storage`
+ `'abstract-float' values cannot appear in input storage`
);
assert(ty.elementType.kind !== 'f64', `'No storage type defined for 'f64' values`);
if (ty.elementType.kind === 'bool') {
- return `${expr} != vec${ty.width}<u32>(0u)`;
+ return `(${expr} != vec${ty.width}<u32>(0u))`;
}
}
+ if (ty instanceof ArrayType && elementTypeOf(ty) === Type.bool) {
+ // array<u32, N> -> array<bool, N>
+ const conv = helpers.uniqueID();
+ const inTy = Type.array(ty.count, Type.u32);
+ helpers.wgsl += `
+fn ${conv}(in : ${inTy}) -> ${ty} {
+ var out : ${ty};
+ for (var i = 0; i < ${ty.count}; i++) {
+ out[i] = in[i] != 0;
+ }
+ return out;
+}
+`;
+ return `${conv}(${expr})`;
+ }
return expr;
}
// Helper for converting a value of the type 'ty' to the storage type.
-function toStorage(ty: Type, expr: string): string {
+function toStorage(ty: Type, expr: string, helpers: TypeConversionHelpers): string {
if (ty instanceof ScalarType) {
assert(
+ ty.kind !== 'abstract-int',
+ `'abstract-int' values have custom code for writing to storage`
+ );
+ assert(
ty.kind !== 'abstract-float',
- `AbstractFloat values have custom code for writing to storage`
+ `'abstract-float' values have custom code for writing to storage`
);
assert(ty.kind !== 'f64', `No storage type defined for 'f64' values`);
if (ty.kind === 'bool') {
@@ -245,14 +256,33 @@ function toStorage(ty: Type, expr: string): string {
}
if (ty instanceof VectorType) {
assert(
+ ty.elementType.kind !== 'abstract-int',
+ `'abstract-int' values have custom code for writing to storage`
+ );
+ assert(
ty.elementType.kind !== 'abstract-float',
- `AbstractFloat values have custom code for writing to storage`
+ `'abstract-float' 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') {
return `select(vec${ty.width}<u32>(0u), vec${ty.width}<u32>(1u), ${expr})`;
}
}
+ if (ty instanceof ArrayType && elementTypeOf(ty) === Type.bool) {
+ // array<bool, N> -> array<u32, N>
+ const conv = helpers.uniqueID();
+ const outTy = Type.array(ty.count, Type.u32);
+ helpers.wgsl += `
+fn ${conv}(in : ${ty}) -> ${outTy} {
+ var out : ${outTy};
+ for (var i = 0; i < ${ty.count}; i++) {
+ out[i] = select(0u, 1u, in[i]);
+ }
+ return out;
+}
+`;
+ return `${conv}(${expr})`;
+ }
return expr;
}
@@ -296,7 +326,7 @@ export async function run(
parameterTypes: Array<Type>,
resultType: Type,
cfg: Config = { inputSource: 'storage_r' },
- cases: CaseList,
+ cases: Case[],
batch_size?: number
) {
// If the 'vectorize' config option was provided, pack the cases into vectors.
@@ -325,12 +355,13 @@ export async function run(
// 2k appears to be a sweet-spot when benchmarking.
return Math.floor(
Math.min(1024 * 2, t.device.limits.maxUniformBufferBindingSize) /
- valueStrides(parameterTypes)
+ structStride(parameterTypes, cfg.inputSource)
);
case 'storage_r':
case 'storage_rw':
return Math.floor(
- t.device.limits.maxStorageBufferBindingSize / valueStrides(parameterTypes)
+ t.device.limits.maxStorageBufferBindingSize /
+ structStride(parameterTypes, cfg.inputSource)
);
}
})();
@@ -353,7 +384,7 @@ export async function run(
}
};
- const processBatch = async (batchCases: CaseList) => {
+ const processBatch = async (batchCases: Case[]) => {
const checkBatch = await submitBatch(
t,
shaderBuilder,
@@ -404,12 +435,13 @@ async function submitBatch(
shaderBuilder: ShaderBuilder,
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
inputSource: InputSource,
pipelineCache: PipelineCache
): Promise<() => void> {
// Construct a buffer to hold the results of the expression tests
- const outputBufferSize = cases.length * valueStride(resultType);
+ const outputStride = structStride([resultType], 'storage_rw');
+ const outputBufferSize = align(cases.length * outputStride, 4);
const outputBuffer = t.device.createBuffer({
size: outputBufferSize,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
@@ -444,7 +476,7 @@ async function submitBatch(
// Read the outputs from the output buffer
const outputs = new Array<Value>(cases.length);
for (let i = 0; i < cases.length; i++) {
- outputs[i] = resultType.read(outputData, i * valueStride(resultType));
+ outputs[i] = resultType.read(outputData, i * outputStride);
}
// The list of expectation failures
@@ -498,7 +530,7 @@ function map<T, U>(v: T | readonly T[], fn: (value: T, index?: number) => U): U[
export type ShaderBuilder = (
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
inputSource: InputSource
) => string;
@@ -507,10 +539,13 @@ export type ShaderBuilder = (
*/
function wgslOutputs(resultType: Type, count: number): string {
let output_struct = undefined;
- if (scalarTypeOf(resultType).kind !== 'abstract-float') {
+ if (
+ scalarTypeOf(resultType).kind !== 'abstract-float' &&
+ scalarTypeOf(resultType).kind !== 'abstract-int'
+ ) {
output_struct = `
struct Output {
- @size(${valueStride(resultType)}) value : ${storageType(resultType)}
+ @size(${strideOf(resultType, 'storage_rw')}) value : ${storageType(resultType)}
};`;
} else {
if (resultType instanceof ScalarType) {
@@ -520,7 +555,7 @@ struct Output {
};
struct Output {
- @size(${valueStride(resultType)}) value: AF,
+ @size(${strideOf(resultType, 'storage_rw')}) value: AF,
};`;
}
if (resultType instanceof VectorType) {
@@ -531,7 +566,7 @@ struct Output {
};
struct Output {
- @size(${valueStride(resultType)}) value: array<AF, ${dim}>,
+ @size(${strideOf(resultType, 'storage_rw')}) value: array<AF, ${dim}>,
};`;
}
@@ -544,7 +579,7 @@ struct Output {
};
struct Output {
- @size(${valueStride(resultType)}) value: array<array<AF, ${rows}>, ${cols}>,
+ @size(${strideOf(resultType, 'storage_rw')}) value: array<array<AF, ${rows}>, ${cols}>,
};`;
}
@@ -562,7 +597,7 @@ struct Output {
function wgslValuesArray(
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
expressionBuilder: ExpressionBuilder
): string {
return `
@@ -612,19 +647,28 @@ function basicExpressionShaderBody(
expressionBuilder: ExpressionBuilder,
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
inputSource: InputSource
): string {
assert(
+ scalarTypeOf(resultType).kind !== 'abstract-int',
+ `abstractIntShaderBuilder should be used when result type is 'abstract-int'`
+ );
+ assert(
scalarTypeOf(resultType).kind !== 'abstract-float',
- `abstractFloatShaderBuilder should be used when result type is 'abstract-float`
+ `abstractFloatShaderBuilder should be used when result type is 'abstract-float'`
);
+ let nextUniqueIDSuffix = 0;
+ const convHelpers: TypeConversionHelpers = {
+ wgsl: '',
+ uniqueID: () => `cts_symbol_${nextUniqueIDSuffix++}`,
+ };
if (inputSource === 'const') {
//////////////////////////////////////////////////////////////////////////
// Constant eval
//////////////////////////////////////////////////////////////////////////
let body = '';
- if (parameterTypes.some(ty => scalarTypeOf(ty).kind === 'abstract-float')) {
+ if (parameterTypes.some(ty => isAbstractType(elementTypeOf(ty)))) {
// Directly assign the expression to the output, to avoid an
// intermediate store, which will concretize the value early
body = cases
@@ -632,7 +676,8 @@ function basicExpressionShaderBody(
(c, i) =>
` outputs[${i}].value = ${toStorage(
resultType,
- expressionBuilder(map(c.input, v => v.wgsl()))
+ expressionBuilder(map(c.input, v => v.wgsl())),
+ convHelpers
)};`
)
.join('\n ');
@@ -640,47 +685,60 @@ function basicExpressionShaderBody(
body = cases
.map((_, i) => {
const value = `values[${i}]`;
- return ` outputs[${i}].value = ${toStorage(resultType, value)};`;
+ return ` outputs[${i}].value = ${toStorage(resultType, value, convHelpers)};`;
})
.join('\n ');
} else {
body = `
for (var i = 0u; i < ${cases.length}; i++) {
- outputs[i].value = ${toStorage(resultType, `values[i]`)};
+ outputs[i].value = ${toStorage(resultType, `values[i]`, convHelpers)};
}`;
}
+ // If params are abstract, we will assign them directly to the storage array, so skip the values array.
+ let valuesArray = '';
+ if (!parameterTypes.some(isAbstractType)) {
+ valuesArray = wgslValuesArray(parameterTypes, resultType, cases, expressionBuilder);
+ }
+
return `
${wgslOutputs(resultType, cases.length)}
-${wgslValuesArray(parameterTypes, resultType, cases, expressionBuilder)}
+${valuesArray}
+
+${convHelpers.wgsl}
@compute @workgroup_size(1)
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}`);
+ const paramExpr = (ty: Type, i: number) => fromStorage(ty, `inputs[i].param${i}`, convHelpers);
// resolves to the expression that calls the builtin
- const expr = toStorage(resultType, expressionBuilder(parameterTypes.map(paramExpr)));
+ const expr = toStorage(
+ resultType,
+ expressionBuilder(parameterTypes.map(paramExpr)),
+ convHelpers
+ );
return `
struct Input {
-${parameterTypes
- .map((ty, i) => ` @size(${valueStride(ty)}) param${i} : ${storageType(ty)},`)
- .join('\n')}
-};
+${wgslMembers(parameterTypes.map(storageType), inputSource, i => `param${i}`)}
+}
${wgslOutputs(resultType, cases.length)}
${wgslInputVar(inputSource, cases.length)}
+${convHelpers.wgsl}
+
@compute @workgroup_size(1)
fn main() {
for (var i = 0; i < ${cases.length}; i++) {
@@ -699,7 +757,7 @@ export function basicExpressionBuilder(expressionBuilder: ExpressionBuilder): Sh
return (
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
inputSource: InputSource
) => {
return `\
@@ -722,7 +780,7 @@ export function basicExpressionWithPredeclarationBuilder(
return (
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
inputSource: InputSource
) => {
return `\
@@ -742,7 +800,7 @@ export function compoundAssignmentBuilder(op: string): ShaderBuilder {
return (
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
inputSource: InputSource
) => {
//////////////////////////////////////////////////////////////////////////
@@ -807,8 +865,7 @@ ${wgslHeader(parameterTypes, resultType)}
${wgslOutputs(resultType, cases.length)}
struct Input {
- @size(${valueStride(lhsType)}) lhs : ${storageType(lhsType)},
- @size(${valueStride(rhsType)}) rhs : ${storageType(rhsType)},
+${wgslMembers([lhsType, rhsType].map(storageType), inputSource, i => ['lhs', 'rhs'][i])}
}
${wgslInputVar(inputSource, cases.length)}
@@ -969,10 +1026,10 @@ export function abstractFloatShaderBuilder(expressionBuilder: ExpressionBuilder)
return (
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
inputSource: InputSource
) => {
- assert(inputSource === 'const', 'AbstractFloat results are only defined for const-eval');
+ assert(inputSource === 'const', `'abstract-float' results are only defined for const-eval`);
assert(
scalarTypeOf(resultType).kind === 'abstract-float',
`Expected resultType of 'abstract-float', received '${scalarTypeOf(resultType).kind}' instead`
@@ -998,6 +1055,90 @@ ${body}
}
/**
+ * @returns a string that extracts the value of an AbstractInt into an output
+ * destination
+ * @param expr expression for an AbstractInt value, if working with vectors,
+ * 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 to the AbstractInt that needs
+ * to be operated on.
+ * For scalars this should be left as ''.
+ * For vectors this will be an indexing operation,
+ * i.e. '[i]'
+ */
+function abstractIntSnippet(expr: string, case_idx: number, accessor: string = ''): string {
+ // AbstractInts are i64s under the hood. WebGPU does not support
+ // putting i64s in buffers, or any 64-bit simple types, so the result needs to
+ // be split up into u32 bitfields
+ //
+ // 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 into arrays.
+ //
+ // This is complicated by the fact that user defined functions cannot
+ // take/return AbstractInts, and AbstractInts 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 AbstractInt.
+ return ` {
+ outputs[${case_idx}].value${accessor}.high = bitcast<u32>(i32(${expr}${accessor} >> 32)) & 0xFFFFFFFF;
+ const low_sign = (${expr}${accessor} & (1 << 31));
+ outputs[${case_idx}].value${accessor}.low = bitcast<u32>((${expr}${accessor} & 0x7FFFFFFF)) | low_sign;
+ }`;
+}
+
+/** @returns a string for a specific case that has a AbstractInt result */
+function abstractIntCaseBody(expr: string, resultType: Type, i: number): string {
+ if (resultType instanceof ScalarType) {
+ return abstractIntSnippet(expr, i);
+ }
+
+ if (resultType instanceof VectorType) {
+ return [...Array(resultType.width).keys()]
+ .map(idx => abstractIntSnippet(expr, i, `[${idx}]`))
+ .join(' \n');
+ }
+
+ unreachable(`Results of type '${resultType}' not yet implemented`);
+}
+
+/**
+ * @returns a ShaderBuilder that builds a test shader hands AbstractInt results.
+ * @param expressionBuilder an expression builder that will return AbstractInts
+ */
+export function abstractIntShaderBuilder(expressionBuilder: ExpressionBuilder): ShaderBuilder {
+ return (
+ parameterTypes: Array<Type>,
+ resultType: Type,
+ cases: Case[],
+ inputSource: InputSource
+ ) => {
+ assert(inputSource === 'const', `'abstract-int' results are only defined for const-eval`);
+ assert(
+ scalarTypeOf(resultType).kind === 'abstract-int',
+ `Expected resultType of 'abstract-int', received '${scalarTypeOf(resultType).kind}' instead`
+ );
+
+ const body = cases
+ .map((c, i) => {
+ const expr = `${expressionBuilder(map(c.input, v => v.wgsl()))}`;
+ return abstractIntCaseBody(expr, resultType, i);
+ })
+ .join('\n ');
+
+ return `
+${wgslHeader(parameterTypes, resultType)}
+
+${wgslOutputs(resultType, cases.length)}
+
+@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
* `pipelineCache`, then this may be returned instead of creating a new
@@ -1016,7 +1157,7 @@ async function buildPipeline(
shaderBuilder: ShaderBuilder,
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
inputSource: InputSource,
outputBuffer: GPUBuffer,
pipelineCache: PipelineCache
@@ -1060,27 +1201,23 @@ async function buildPipeline(
// Input values come from a uniform or storage buffer
// size in bytes of the input buffer
- const inputSize = cases.length * valueStrides(parameterTypes);
+ const caseStride = structStride(parameterTypes, inputSource);
+ const inputSize = align(cases.length * caseStride, 4);
// Holds all the parameter values for all cases
const inputData = new Uint8Array(inputSize);
// Pack all the input parameter values into the inputData buffer
- {
- const caseStride = valueStrides(parameterTypes);
- for (let caseIdx = 0; caseIdx < cases.length; caseIdx++) {
- const caseBase = caseIdx * caseStride;
- let offset = caseBase;
- for (let paramIdx = 0; paramIdx < parameterTypes.length; paramIdx++) {
- const params = cases[caseIdx].input;
- if (params instanceof Array) {
- params[paramIdx].copyTo(inputData, offset);
- } else {
- params.copyTo(inputData, offset);
- }
- offset += valueStride(parameterTypes[paramIdx]);
+ for (let caseIdx = 0; caseIdx < cases.length; caseIdx++) {
+ const offset = caseIdx * caseStride;
+ structLayout(parameterTypes, inputSource, m => {
+ const arg = cases[caseIdx].input;
+ if (arg instanceof Array) {
+ arg[m.index].copyTo(inputData, offset + m.offset);
+ } else {
+ arg.copyTo(inputData, offset + m.offset);
}
- }
+ });
}
// build the compute pipeline, if the shader hasn't been compiled already.
@@ -1123,12 +1260,12 @@ async function buildPipeline(
* If `cases.length` is not a multiple of `vectorWidth`, then the last scalar
* test case value is repeated to fill the vector value.
*/
-function packScalarsToVector(
+export function packScalarsToVector(
parameterTypes: Array<Type>,
resultType: Type,
- cases: CaseList,
+ cases: Case[],
vectorWidth: number
-): { cases: CaseList; parameterTypes: Array<Type>; resultType: Type } {
+): { cases: Case[]; parameterTypes: Array<Type>; resultType: Type } {
// Validate that the parameters and return type are all vectorizable
for (let i = 0; i < parameterTypes.length; i++) {
const ty = parameterTypes[i];
@@ -1145,22 +1282,22 @@ function packScalarsToVector(
}
const packedCases: Array<Case> = [];
- const packedParameterTypes = parameterTypes.map(p => TypeVec(vectorWidth, p as ScalarType));
- const packedResultType = new VectorType(vectorWidth, resultType);
+ const packedParameterTypes = parameterTypes.map(p => Type.vec(vectorWidth, p as ScalarType));
+ const packedResultType = Type.vec(vectorWidth, resultType);
const clampCaseIdx = (idx: number) => Math.min(idx, cases.length - 1);
let caseIdx = 0;
while (caseIdx < cases.length) {
// Construct the vectorized inputs from the scalar cases
- const packedInputs = new Array<Vector>(parameterTypes.length);
+ const packedInputs = new Array<VectorValue>(parameterTypes.length);
for (let paramIdx = 0; paramIdx < parameterTypes.length; paramIdx++) {
- const inputElements = new Array<Scalar>(vectorWidth);
+ const inputElements = new Array<ScalarValue>(vectorWidth);
for (let i = 0; i < vectorWidth; i++) {
const input = cases[clampCaseIdx(caseIdx + i)].input;
- inputElements[i] = (input instanceof Array ? input[paramIdx] : input) as Scalar;
+ inputElements[i] = (input instanceof Array ? input[paramIdx] : input) as ScalarValue;
}
- packedInputs[paramIdx] = new Vector(inputElements);
+ packedInputs[paramIdx] = new VectorValue(inputElements);
}
// Gather the comparators for the packed cases
@@ -1174,7 +1311,7 @@ function packScalarsToVector(
const gElements = new Array<string>(vectorWidth);
const eElements = new Array<string>(vectorWidth);
for (let i = 0; i < vectorWidth; i++) {
- const d = cmp_impls[i]((got as Vector).elements[i]);
+ const d = cmp_impls[i]((got as VectorValue).elements[i]);
matched = matched && d.matched;
gElements[i] = d.got;
eElements[i] = d.expected;
@@ -1199,238 +1336,3 @@ function packScalarsToVector(
resultType: packedResultType,
};
}
-
-/**
- * Indicates bounds that acceptance intervals need to be within to avoid inputs
- * being filtered out. This is used for const-eval tests, since going OOB will
- * cause a validation error not an execution error.
- */
-export type IntervalFilter =
- | 'finite' // Expected to be finite in the interval numeric space
- | 'unfiltered'; // No expectations
-
-/**
- * A function that performs a binary operation on x and y, and returns the expected
- * result.
- */
-export interface BinaryOp {
- (x: number, y: number): number | undefined;
-}
-
-/**
- * @returns array of Case for the input params with op applied
- * @param param0s array of inputs to try for the first param
- * @param param1s array of inputs to try for the second param
- * @param op callback called on each pair of inputs to produce each case
- * @param quantize function to quantize all values
- * @param scalarize function to convert numbers to Scalars
- */
-function generateScalarBinaryToScalarCases(
- param0s: readonly number[],
- param1s: readonly number[],
- op: BinaryOp,
- quantize: QuantizeFunc,
- scalarize: ScalarBuilder
-): Case[] {
- param0s = param0s.map(quantize);
- param1s = param1s.map(quantize);
- return cartesianProduct(param0s, param1s).reduce((cases, e) => {
- const expected = op(e[0], e[1]);
- if (expected !== undefined) {
- cases.push({ input: [scalarize(e[0]), scalarize(e[1])], expected: scalarize(expected) });
- }
- return cases;
- }, new Array<Case>());
-}
-
-/**
- * @returns an array of Cases for operations over a range of inputs
- * @param param0s array of inputs to try for the first param
- * @param param1s array of inputs to try for the second param
- * @param op callback called on each pair of inputs to produce each case
- */
-export function generateBinaryToI32Cases(
- param0s: readonly number[],
- param1s: readonly number[],
- op: BinaryOp
-) {
- return generateScalarBinaryToScalarCases(param0s, param1s, op, quantizeToI32, i32);
-}
-
-/**
- * @returns an array of Cases for operations over a range of inputs
- * @param param0s array of inputs to try for the first param
- * @param param1s array of inputs to try for the second param
- * @param op callback called on each pair of inputs to produce each case
- */
-export function generateBinaryToU32Cases(
- param0s: readonly number[],
- param1s: readonly number[],
- op: BinaryOp
-) {
- return generateScalarBinaryToScalarCases(param0s, param1s, op, quantizeToU32, u32);
-}
-
-/**
- * @returns a Case for the input params with op applied
- * @param scalar scalar param
- * @param vector vector param (2, 3, or 4 elements)
- * @param op the op to apply to scalar and vector
- * @param quantize function to quantize all values in vectors and scalars
- * @param scalarize function to convert numbers to Scalars
- */
-function makeScalarVectorBinaryToVectorCase(
- scalar: number,
- vector: readonly number[],
- op: BinaryOp,
- quantize: QuantizeFunc,
- scalarize: ScalarBuilder
-): Case | undefined {
- scalar = quantize(scalar);
- vector = vector.map(quantize);
- const result = vector.map(v => op(scalar, v));
- if (result.includes(undefined)) {
- return undefined;
- }
- return {
- input: [scalarize(scalar), new Vector(vector.map(scalarize))],
- expected: new Vector((result as readonly number[]).map(scalarize)),
- };
-}
-
-/**
- * @returns array of Case for the input params with op applied
- * @param scalars array of scalar params
- * @param vectors array of vector params (2, 3, or 4 elements)
- * @param op the op to apply to each pair of scalar and vector
- * @param quantize function to quantize all values in vectors and scalars
- * @param scalarize function to convert numbers to Scalars
- */
-function generateScalarVectorBinaryToVectorCases(
- scalars: readonly number[],
- vectors: ROArrayArray<number>,
- op: BinaryOp,
- quantize: QuantizeFunc,
- scalarize: ScalarBuilder
-): Case[] {
- const cases = new Array<Case>();
- scalars.forEach(s => {
- vectors.forEach(v => {
- const c = makeScalarVectorBinaryToVectorCase(s, v, op, quantize, scalarize);
- if (c !== undefined) {
- cases.push(c);
- }
- });
- });
- return cases;
-}
-
-/**
- * @returns a Case for the input params with op applied
- * @param vector vector param (2, 3, or 4 elements)
- * @param scalar scalar param
- * @param op the op to apply to vector and scalar
- * @param quantize function to quantize all values in vectors and scalars
- * @param scalarize function to convert numbers to Scalars
- */
-function makeVectorScalarBinaryToVectorCase(
- vector: readonly number[],
- scalar: number,
- op: BinaryOp,
- quantize: QuantizeFunc,
- scalarize: ScalarBuilder
-): Case | undefined {
- vector = vector.map(quantize);
- scalar = quantize(scalar);
- const result = vector.map(v => op(v, scalar));
- if (result.includes(undefined)) {
- return undefined;
- }
- return {
- input: [new Vector(vector.map(scalarize)), scalarize(scalar)],
- expected: new Vector((result as readonly number[]).map(scalarize)),
- };
-}
-
-/**
- * @returns array of Case for the input params with op applied
- * @param vectors array of vector params (2, 3, or 4 elements)
- * @param scalars array of scalar params
- * @param op the op to apply to each pair of vector and scalar
- * @param quantize function to quantize all values in vectors and scalars
- * @param scalarize function to convert numbers to Scalars
- */
-function generateVectorScalarBinaryToVectorCases(
- vectors: ROArrayArray<number>,
- scalars: readonly number[],
- op: BinaryOp,
- quantize: QuantizeFunc,
- scalarize: ScalarBuilder
-): Case[] {
- const cases = new Array<Case>();
- scalars.forEach(s => {
- vectors.forEach(v => {
- const c = makeVectorScalarBinaryToVectorCase(v, s, op, quantize, scalarize);
- if (c !== undefined) {
- cases.push(c);
- }
- });
- });
- return cases;
-}
-
-/**
- * @returns array of Case for the input params with op applied
- * @param scalars array of scalar params
- * @param vectors array of vector params (2, 3, or 4 elements)
- * @param op he op to apply to each pair of scalar and vector
- */
-export function generateU32VectorBinaryToVectorCases(
- scalars: readonly number[],
- vectors: ROArrayArray<number>,
- op: BinaryOp
-): Case[] {
- return generateScalarVectorBinaryToVectorCases(scalars, vectors, op, quantizeToU32, u32);
-}
-
-/**
- * @returns array of Case for the input params with op applied
- * @param vectors array of vector params (2, 3, or 4 elements)
- * @param scalars array of scalar params
- * @param op he op to apply to each pair of vector and scalar
- */
-export function generateVectorU32BinaryToVectorCases(
- vectors: ROArrayArray<number>,
- scalars: readonly number[],
- op: BinaryOp
-): Case[] {
- return generateVectorScalarBinaryToVectorCases(vectors, scalars, op, quantizeToU32, u32);
-}
-
-/**
- * @returns array of Case for the input params with op applied
- * @param scalars array of scalar params
- * @param vectors array of vector params (2, 3, or 4 elements)
- * @param op he op to apply to each pair of scalar and vector
- */
-export function generateI32VectorBinaryToVectorCases(
- scalars: readonly number[],
- vectors: ROArrayArray<number>,
- op: BinaryOp
-): Case[] {
- return generateScalarVectorBinaryToVectorCases(scalars, vectors, op, quantizeToI32, i32);
-}
-
-/**
- * @returns array of Case for the input params with op applied
- * @param vectors array of vector params (2, 3, or 4 elements)
- * @param scalars array of scalar params
- * @param op he op to apply to each pair of vector and scalar
- */
-export function generateVectorI32BinaryToVectorCases(
- vectors: ROArrayArray<number>,
- scalars: readonly number[],
- op: BinaryOp
-): Case[] {
- return generateVectorScalarBinaryToVectorCases(vectors, scalars, op, quantizeToI32, i32);
-}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/interval_filter.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/interval_filter.ts
new file mode 100644
index 0000000000..7471247e54
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/interval_filter.ts
@@ -0,0 +1,8 @@
+/**
+ * Indicates bounds that acceptance intervals need to be within to avoid inputs
+ * being filtered out. This is used for const-eval tests, since going OOB will
+ * cause a validation error not an execution error.
+ */
+export type IntervalFilter =
+ | 'finite' // Expected to be finite in the interval numeric space
+ | 'unfiltered'; // No expectations
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/precedence.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/precedence.spec.ts
new file mode 100644
index 0000000000..643bda657e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/precedence.spec.ts
@@ -0,0 +1,113 @@
+export const description = `
+Execution tests for operator precedence.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// The list of test cases and their expected results.
+interface Expression {
+ expr: string;
+ result: number;
+}
+const kExpressions: Record<string, Expression> = {
+ add_mul: { expr: 'kThree + kSeven * kEleven', result: 80 },
+ mul_add: { expr: 'kThree * kSeven + kEleven', result: 32 },
+ sub_neg: { expr: 'kThree - - kSeven', result: 10 },
+ neg_shl: { expr: '- kThree << u32(kSeven)', result: -384 },
+ neg_shr: { expr: '- kThree >> u32(kSeven)', result: -1 },
+ neg_add: { expr: '- kThree + kSeven', result: 4 },
+ neg_mul: { expr: '- kThree * kSeven', result: -21 },
+ neg_and: { expr: '- kThree & kSeven', result: 5 },
+ neg_or: { expr: '- kThree | kSeven', result: -1 },
+ neg_xor: { expr: '- kThree ^ kSeven', result: -6 },
+ comp_add: { expr: '~ kThree + kSeven', result: 3 },
+ mul_deref: { expr: 'kThree * * ptr_five', result: 15 },
+ not_and: { expr: 'i32(! kFalse && kFalse)', result: 0 },
+ not_or: { expr: 'i32(! kTrue || kTrue)', result: 1 },
+ eq_and: { expr: 'i32(kFalse == kTrue && kFalse)', result: 0 },
+ and_eq: { expr: 'i32(kFalse && kTrue == kFalse)', result: 0 },
+ eq_or: { expr: 'i32(kFalse == kFalse || kTrue)', result: 1 },
+ or_eq: { expr: 'i32(kTrue || kFalse == kFalse)', result: 1 },
+ add_swizzle: { expr: '(vec + vec . y) . z', result: 8 },
+};
+
+g.test('precedence')
+ .desc(
+ `
+ Test that operator precedence rules are correctly implemented.
+ `
+ )
+ .params(u =>
+ u
+ .combine('expr', keysOf(kExpressions))
+ .combine('decl', ['literal', 'const', 'override', 'var<private>'])
+ .combine('strip_spaces', [false, true])
+ )
+ .fn(t => {
+ const expr = kExpressions[t.params.expr];
+
+ let decl = t.params.decl;
+ let expr_wgsl = expr.expr;
+ if (t.params.decl === 'literal') {
+ decl = 'const';
+ expr_wgsl = expr_wgsl.replace(/kThree/g, '3');
+ expr_wgsl = expr_wgsl.replace(/kSeven/g, '7');
+ expr_wgsl = expr_wgsl.replace(/kEleven/g, '11');
+ expr_wgsl = expr_wgsl.replace(/kFalse/g, 'false');
+ expr_wgsl = expr_wgsl.replace(/kTrue/g, 'true');
+ }
+ if (t.params.strip_spaces) {
+ expr_wgsl = expr_wgsl.replace(/ /g, '');
+ }
+ const wgsl = `
+ @group(0) @binding(0) var<storage, read_write> buffer : i32;
+
+ ${decl} kFalse = false;
+ ${decl} kTrue = true;
+
+ ${decl} kThree = 3;
+ ${decl} kSeven = 7;
+ ${decl} kEleven = 11;
+
+ @compute @workgroup_size(1)
+ fn main() {
+ var five = 5;
+ var vec = vec4(1, kThree, 5, kSeven);
+ let ptr_five = &five;
+
+ buffer = ${expr_wgsl};
+ }
+ `;
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ },
+ });
+
+ // Allocate a buffer and fill it with 0xdeadbeef.
+ const outputBuffer = t.makeBufferWithContents(
+ new Uint32Array([0xdeadbeef]),
+ GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
+ );
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: outputBuffer } }],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Check that the result is as expected.
+ t.expectGPUBufferValuesEqual(outputBuffer, new Int32Array([expr.result]));
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/address_of_and_indirection.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/address_of_and_indirection.spec.ts
new file mode 100644
index 0000000000..c4961558e0
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/address_of_and_indirection.spec.ts
@@ -0,0 +1,171 @@
+export const description = `
+Execution Tests for unary address-of and indirection (dereference)
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../common/util/data_tables.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { ScalarKind, scalarType } from '../../../../util/conversion.js';
+import { sparseScalarF32Range } from '../../../../util/math.js';
+import {
+ allButConstInputSource,
+ basicExpressionWithPredeclarationBuilder,
+ run,
+} from '../expression.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// All the ways to deref an expression
+const kDerefCases = {
+ deref_address_of_identifier: {
+ wgsl: '(*(&a))',
+ requires_pointer_composite_access: false,
+ },
+ deref_pointer: {
+ wgsl: '(*p)',
+ requires_pointer_composite_access: false,
+ },
+ address_of_identifier: {
+ wgsl: '(&a)',
+ requires_pointer_composite_access: true,
+ },
+ pointer: {
+ wgsl: 'p',
+ requires_pointer_composite_access: true,
+ },
+};
+
+g.test('deref')
+ .specURL('https://www.w3.org/TR/WGSL/#indirection')
+ .desc(
+ `
+Expression: *e
+
+Pointer expression dereference.
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', allButConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('scalarType', ['bool', 'u32', 'i32', 'f32', 'f16'] as ScalarKind[])
+ .combine('derefType', keysOf(kDerefCases))
+ .filter(p => !kDerefCases[p.derefType].requires_pointer_composite_access)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.scalarType === 'f16') {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ }
+ })
+ .fn(async t => {
+ const ty = scalarType(t.params.scalarType);
+ const cases = sparseScalarF32Range().map(e => {
+ return { input: ty.create(e), expected: ty.create(e) };
+ });
+ const elemType = ty.kind;
+ const type = t.params.vectorize ? `vec${t.params.vectorize}<${elemType}>` : elemType;
+ const shaderBuilder = basicExpressionWithPredeclarationBuilder(
+ value => `get_dereferenced_value(${value})`,
+ `fn get_dereferenced_value(value: ${type}) -> ${type} {
+ var a = value;
+ let p = &a;
+ return ${kDerefCases[t.params.derefType].wgsl};
+ }`
+ );
+ await run(t, shaderBuilder, [ty], ty, t.params, cases);
+ });
+
+g.test('deref_index')
+ .specURL('https://www.w3.org/TR/WGSL/#logical-expr')
+ .desc(
+ `
+Expression: (*e)[index]
+
+Pointer expression dereference as lhs of index accessor expression
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', allButConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('scalarType', ['bool', 'u32', 'i32', 'f32', 'f16'] as ScalarKind[])
+ .combine('derefType', keysOf(kDerefCases))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.scalarType === 'f16') {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ }
+ })
+ .fn(async t => {
+ if (
+ kDerefCases[t.params.derefType].requires_pointer_composite_access &&
+ !t.hasLanguageFeature('pointer_composite_access')
+ ) {
+ return;
+ }
+
+ const ty = scalarType(t.params.scalarType);
+ const cases = sparseScalarF32Range().map(e => {
+ return { input: ty.create(e), expected: ty.create(e) };
+ });
+ const elemType = ty.kind;
+ const type = t.params.vectorize ? `vec${t.params.vectorize}<${elemType}>` : elemType;
+ const shaderBuilder = basicExpressionWithPredeclarationBuilder(
+ value => `get_dereferenced_value(${value})`,
+ `fn get_dereferenced_value(value: ${type}) -> ${type} {
+ var a = array<${type}, 1>(value);
+ let p = &a;
+ return ${kDerefCases[t.params.derefType].wgsl}[0];
+ }`
+ );
+ await run(t, shaderBuilder, [ty], ty, t.params, cases);
+ });
+
+g.test('deref_member')
+ .specURL('https://www.w3.org/TR/WGSL/#logical-expr')
+ .desc(
+ `
+Expression: (*e).member
+
+Pointer expression dereference as lhs of member accessor expression
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', allButConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ .combine('scalarType', ['bool', 'u32', 'i32', 'f32', 'f16'] as ScalarKind[])
+ .combine('derefType', keysOf(kDerefCases))
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.scalarType === 'f16') {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ }
+ })
+ .fn(async t => {
+ if (
+ kDerefCases[t.params.derefType].requires_pointer_composite_access &&
+ !t.hasLanguageFeature('pointer_composite_access')
+ ) {
+ return;
+ }
+
+ const ty = scalarType(t.params.scalarType);
+ const cases = sparseScalarF32Range().map(e => {
+ return { input: ty.create(e), expected: ty.create(e) };
+ });
+ const elemType = ty.kind;
+ const type = t.params.vectorize ? `vec${t.params.vectorize}<${elemType}>` : elemType;
+ const shaderBuilder = basicExpressionWithPredeclarationBuilder(
+ value => `get_dereferenced_value(${value})`,
+ `struct S {
+ m : ${type}
+ }
+ fn get_dereferenced_value(value: ${type}) -> ${type} {
+ var a = S(value);
+ let p = &a;
+ return ${kDerefCases[t.params.derefType].wgsl}.m;
+ }`
+ );
+ await run(t, shaderBuilder, [ty], ty, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.cache.ts
new file mode 100644
index 0000000000..4f274e8922
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.cache.ts
@@ -0,0 +1,13 @@
+import { FP } from '../../../../util/floating_point.js';
+import { scalarF64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/af_arithmetic', {
+ negation: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ scalarF64Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }),
+ 'unfiltered',
+ FP.abstract.negationInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts
index 182c0d76a9..686d4b7c4a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_arithmetic.spec.ts
@@ -1,29 +1,17 @@
export const description = `
-Execution Tests for AbstractFloat arithmetic unary expression operations
+Execution Tests for Type.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 { Type } from '../../../../util/conversion.js';
import { onlyConstInputSource, run } from '../expression.js';
-import { abstractUnary } from './unary.js';
+import { d } from './af_arithmetic.cache.js';
+import { abstractFloatUnary } 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(
@@ -39,5 +27,13 @@ Accuracy: Correctly rounded
)
.fn(async t => {
const cases = await d.get('negation');
- await run(t, abstractUnary('-'), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases, 1);
+ await run(
+ t,
+ abstractFloatUnary('-'),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases,
+ 1
+ );
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.cache.ts
new file mode 100644
index 0000000000..7c607927fe
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.cache.ts
@@ -0,0 +1,51 @@
+import { kValue } from '../../../../util/constants.js';
+import { abstractFloat } from '../../../../util/conversion.js';
+import { FP } from '../../../../util/floating_point.js';
+import {
+ isSubnormalNumberF64,
+ limitedScalarF64Range,
+ scalarF64Range,
+} from '../../../../util/math.js';
+import { reinterpretU64AsF64 } from '../../../../util/reinterpret.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/af_assignment', {
+ abstract: () => {
+ const inputs = [
+ // Values that are useful for debugging the underlying framework/shader code, since it cannot be directly unit tested.
+ 0,
+ 0.5,
+ 0.5,
+ 1,
+ -1,
+ reinterpretU64AsF64(0x7000_0000_0000_0001n), // smallest magnitude negative subnormal with non-zero mantissa
+ reinterpretU64AsF64(0x0000_0000_0000_0001n), // smallest magnitude positive subnormal with non-zero mantissa
+ reinterpretU64AsF64(0x600a_aaaa_5555_5555n), // negative subnormal with obvious pattern
+ reinterpretU64AsF64(0x000a_aaaa_5555_5555n), // positive subnormal with obvious pattern
+ reinterpretU64AsF64(0x0010_0000_0000_0001n), // smallest magnitude negative normal with non-zero mantissa
+ reinterpretU64AsF64(0x0010_0000_0000_0001n), // smallest magnitude positive normal with non-zero mantissa
+ reinterpretU64AsF64(0xf555_5555_aaaa_aaaan), // negative normal with obvious pattern
+ reinterpretU64AsF64(0x5555_5555_aaaa_aaaan), // positive normal with obvious pattern
+ reinterpretU64AsF64(0xffef_ffff_ffff_ffffn), // largest magnitude negative normal
+ reinterpretU64AsF64(0x7fef_ffff_ffff_ffffn), // largest magnitude positive normal
+ // WebGPU implementation stressing values
+ ...scalarF64Range(),
+ ];
+ return inputs.map(f => {
+ return {
+ input: abstractFloat(f),
+ expected: isSubnormalNumberF64(f) ? abstractFloat(0) : abstractFloat(f),
+ };
+ });
+ },
+ f32: () => {
+ return limitedScalarF64Range(kValue.f32.negative.min, kValue.f32.positive.max).map(f => {
+ return { input: abstractFloat(f), expected: FP.f32.correctlyRoundedInterval(f) };
+ });
+ },
+ f16: () => {
+ return limitedScalarF64Range(kValue.f16.negative.min, kValue.f16.positive.max).map(f => {
+ return { input: abstractFloat(f), expected: FP.f16.correctlyRoundedInterval(f) };
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts
index 141d87d0f2..001c47e117 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/af_assignment.spec.ts
@@ -4,20 +4,17 @@ Execution Tests for assignment of AbstractFloats
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { kValue } from '../../../../util/constants.js';
-import { abstractFloat, TypeAbstractFloat, TypeF16, TypeF32 } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { filteredF64Range, fullF64Range, isSubnormalNumberF64 } from '../../../../util/math.js';
-import { reinterpretU64AsF64 } from '../../../../util/reinterpret.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import {
+ ShaderBuilder,
abstractFloatShaderBuilder,
basicExpressionBuilder,
onlyConstInputSource,
run,
- ShaderBuilder,
} from '../expression.js';
+import { d } from './af_assignment.cache.js';
+
function concrete_assignment(): ShaderBuilder {
return basicExpressionBuilder(value => `${value}`);
}
@@ -28,47 +25,6 @@ function abstract_assignment(): ShaderBuilder {
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unary/af_assignment', {
- abstract: () => {
- const inputs = [
- // Values that are useful for debugging the underlying framework/shader code, since it cannot be directly unit tested.
- 0,
- 0.5,
- 0.5,
- 1,
- -1,
- reinterpretU64AsF64(0x7000_0000_0000_0001n), // smallest magnitude negative subnormal with non-zero mantissa
- reinterpretU64AsF64(0x0000_0000_0000_0001n), // smallest magnitude positive subnormal with non-zero mantissa
- reinterpretU64AsF64(0x600a_aaaa_5555_5555n), // negative subnormal with obvious pattern
- reinterpretU64AsF64(0x000a_aaaa_5555_5555n), // positive subnormal with obvious pattern
- reinterpretU64AsF64(0x0010_0000_0000_0001n), // smallest magnitude negative normal with non-zero mantissa
- reinterpretU64AsF64(0x0010_0000_0000_0001n), // smallest magnitude positive normal with non-zero mantissa
- reinterpretU64AsF64(0xf555_5555_aaaa_aaaan), // negative normal with obvious pattern
- reinterpretU64AsF64(0x5555_5555_aaaa_aaaan), // positive normal with obvious pattern
- reinterpretU64AsF64(0xffef_ffff_ffff_ffffn), // largest magnitude negative normal
- reinterpretU64AsF64(0x7fef_ffff_ffff_ffffn), // largest magnitude positive normal
- // WebGPU implementation stressing values
- ...fullF64Range(),
- ];
- return inputs.map(f => {
- return {
- input: abstractFloat(f),
- expected: isSubnormalNumberF64(f) ? abstractFloat(0) : abstractFloat(f),
- };
- });
- },
- f32: () => {
- return filteredF64Range(kValue.f32.negative.min, kValue.f32.positive.max).map(f => {
- return { input: abstractFloat(f), expected: FP.f32.correctlyRoundedInterval(f) };
- });
- },
- f16: () => {
- return filteredF64Range(kValue.f16.negative.min, kValue.f16.positive.max).map(f => {
- return { input: abstractFloat(f), expected: FP.f16.correctlyRoundedInterval(f) };
- });
- },
-});
-
g.test('abstract')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-conversion')
.desc(
@@ -79,7 +35,15 @@ testing that extracting abstract floats works
.params(u => u.combine('inputSource', onlyConstInputSource))
.fn(async t => {
const cases = await d.get('abstract');
- await run(t, abstract_assignment(), [TypeAbstractFloat], TypeAbstractFloat, t.params, cases, 1);
+ await run(
+ t,
+ abstract_assignment(),
+ [Type.abstractFloat],
+ Type.abstractFloat,
+ t.params,
+ cases,
+ 1
+ );
});
g.test('f32')
@@ -92,7 +56,7 @@ concretizing to f32
.params(u => u.combine('inputSource', onlyConstInputSource))
.fn(async t => {
const cases = await d.get('f32');
- await run(t, concrete_assignment(), [TypeAbstractFloat], TypeF32, t.params, cases);
+ await run(t, concrete_assignment(), [Type.abstractFloat], Type.f32, t.params, cases);
});
g.test('f16')
@@ -108,5 +72,5 @@ concretizing to f16
.params(u => u.combine('inputSource', onlyConstInputSource))
.fn(async t => {
const cases = await d.get('f16');
- await run(t, concrete_assignment(), [TypeAbstractFloat], TypeF16, t.params, cases);
+ await run(t, concrete_assignment(), [Type.abstractFloat], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.cache.ts
new file mode 100644
index 0000000000..904c0bc700
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.cache.ts
@@ -0,0 +1,11 @@
+import { abstractInt } from '../../../../util/conversion.js';
+import { fullI64Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/ai_arithmetic', {
+ negation: () => {
+ return fullI64Range().map(e => {
+ return { input: abstractInt(e), expected: abstractInt(-e) };
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.spec.ts
new file mode 100644
index 0000000000..625a38b2d5
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_arithmetic.spec.ts
@@ -0,0 +1,30 @@
+export const description = `
+Execution Tests for the abstract integer arithmetic unary expression operations
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { Type } from '../../../../util/conversion.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { d } from './ai_arithmetic.cache.js';
+import { abstractIntUnary } from './unary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('negation')
+ .specURL('https://www.w3.org/TR/WGSL/#arithmetic-expr')
+ .desc(
+ `
+Expression: -x
+`
+ )
+ .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, abstractIntUnary('-'), [Type.abstractInt], Type.abstractInt, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.cache.ts
new file mode 100644
index 0000000000..0fa4f2efc2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.cache.ts
@@ -0,0 +1,21 @@
+import { abstractInt, i32, u32 } from '../../../../util/conversion.js';
+import { fullI32Range, fullI64Range, fullU32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/ai_assignment', {
+ abstract: () => {
+ return fullI64Range().map(n => {
+ return { input: abstractInt(n), expected: abstractInt(n) };
+ });
+ },
+ i32: () => {
+ return fullI32Range().map(n => {
+ return { input: abstractInt(BigInt(n)), expected: i32(n) };
+ });
+ },
+ u32: () => {
+ return fullU32Range().map(n => {
+ return { input: abstractInt(BigInt(n)), expected: u32(n) };
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.spec.ts
new file mode 100644
index 0000000000..fe1ba2d9fc
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_assignment.spec.ts
@@ -0,0 +1,65 @@
+export const description = `
+Execution Tests for assignment of AbstractInts
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { Type } from '../../../../util/conversion.js';
+import {
+ ShaderBuilder,
+ abstractIntShaderBuilder,
+ basicExpressionBuilder,
+ onlyConstInputSource,
+ run,
+} from '../expression.js';
+
+import { d } from './ai_assignment.cache.js';
+
+function concrete_assignment(): ShaderBuilder {
+ return basicExpressionBuilder(value => `${value}`);
+}
+
+function abstract_assignment(): ShaderBuilder {
+ return abstractIntShaderBuilder(value => `${value}`);
+}
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('abstract')
+ .specURL('https://www.w3.org/TR/WGSL/#abstract-types')
+ .desc(
+ `
+testing that extracting abstract ints works
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('abstract');
+ await run(t, abstract_assignment(), [Type.abstractInt], Type.abstractInt, t.params, cases, 1);
+ });
+
+g.test('i32')
+ .specURL('https://www.w3.org/TR/WGSL/#i32-builtin')
+ .desc(
+ `
+concretizing to i32
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('i32');
+ await run(t, concrete_assignment(), [Type.abstractInt], Type.i32, t.params, cases);
+ });
+
+g.test('u32')
+ .specURL('https://www.w3.org/TR/WGSL/#u32-builtin')
+ .desc(
+ `
+concretizing to u32
+`
+ )
+ .params(u => u.combine('inputSource', onlyConstInputSource))
+ .fn(async t => {
+ const cases = await d.get('u32');
+ await run(t, concrete_assignment(), [Type.abstractInt], Type.u32, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_complement.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_complement.spec.ts
new file mode 100644
index 0000000000..507ae219cb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/ai_complement.spec.ts
@@ -0,0 +1,32 @@
+export const description = `
+Execution Tests for the Type.abstractInt bitwise complement operation
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { GPUTest } from '../../../../gpu_test.js';
+import { abstractInt, Type } from '../../../../util/conversion.js';
+import { fullI64Range } from '../../../../util/math.js';
+import { onlyConstInputSource, run } from '../expression.js';
+
+import { abstractIntUnary } from './unary.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('complement')
+ .specURL('https://www.w3.org/TR/WGSL/#bit-expr')
+ .desc(
+ `
+Expression: ~x
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = fullI64Range().map(e => {
+ return { input: abstractInt(e), expected: abstractInt(~e) };
+ });
+ await run(t, abstractIntUnary('~'), [Type.abstractInt], Type.abstractInt, t.params, cases);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.cache.ts
new file mode 100644
index 0000000000..5b16c49c42
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.cache.ts
@@ -0,0 +1,54 @@
+import { anyOf } from '../../../../util/compare.js';
+import { ScalarValue, bool, f16, f32, i32, u32 } from '../../../../util/conversion.js';
+import {
+ fullI32Range,
+ fullU32Range,
+ isSubnormalNumberF16,
+ isSubnormalNumberF32,
+ scalarF16Range,
+ scalarF32Range,
+} from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/bool_conversion', {
+ bool: () => {
+ return [
+ { input: bool(true), expected: bool(true) },
+ { input: bool(false), expected: bool(false) },
+ ];
+ },
+ u32: () => {
+ return fullU32Range().map(u => {
+ return { input: u32(u), expected: u === 0 ? bool(false) : bool(true) };
+ });
+ },
+ i32: () => {
+ return fullI32Range().map(i => {
+ return { input: i32(i), expected: i === 0 ? bool(false) : bool(true) };
+ });
+ },
+ f32: () => {
+ return scalarF32Range().map(f => {
+ const expected: ScalarValue[] = [];
+ if (f !== 0) {
+ expected.push(bool(true));
+ }
+ if (isSubnormalNumberF32(f)) {
+ expected.push(bool(false));
+ }
+ return { input: f32(f), expected: anyOf(...expected) };
+ });
+ },
+ f16: () => {
+ return scalarF16Range().map(f => {
+ const expected: ScalarValue[] = [];
+ if (f !== 0) {
+ expected.push(bool(true));
+ }
+ if (isSubnormalNumberF16(f)) {
+ expected.push(bool(false));
+ }
+ return { input: f16(f), expected: anyOf(...expected) };
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.spec.ts
index 8fcfed339f..55d01d3eda 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_conversion.spec.ts
@@ -4,78 +4,14 @@ Execution Tests for the boolean conversion operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { anyOf } from '../../../../util/compare.js';
-import {
- bool,
- f32,
- f16,
- i32,
- Scalar,
- TypeBool,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeU32,
- u32,
-} from '../../../../util/conversion.js';
-import {
- fullF32Range,
- fullF16Range,
- fullI32Range,
- fullU32Range,
- isSubnormalNumberF32,
- isSubnormalNumberF16,
-} from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, run, ShaderBuilder } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { ShaderBuilder, allInputSources, run } from '../expression.js';
+import { d } from './bool_conversion.cache.js';
import { unary } from './unary.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unary/bool_conversion', {
- bool: () => {
- return [
- { input: bool(true), expected: bool(true) },
- { input: bool(false), expected: bool(false) },
- ];
- },
- u32: () => {
- return fullU32Range().map(u => {
- return { input: u32(u), expected: u === 0 ? bool(false) : bool(true) };
- });
- },
- i32: () => {
- return fullI32Range().map(i => {
- return { input: i32(i), expected: i === 0 ? bool(false) : bool(true) };
- });
- },
- f32: () => {
- return fullF32Range().map(f => {
- const expected: Scalar[] = [];
- if (f !== 0) {
- expected.push(bool(true));
- }
- if (isSubnormalNumberF32(f)) {
- expected.push(bool(false));
- }
- return { input: f32(f), expected: anyOf(...expected) };
- });
- },
- f16: () => {
- return fullF16Range().map(f => {
- const expected: Scalar[] = [];
- if (f !== 0) {
- expected.push(bool(true));
- }
- if (isSubnormalNumberF16(f)) {
- expected.push(bool(false));
- }
- return { input: f16(f), expected: anyOf(...expected) };
- });
- },
-});
-
/** Generate expression builder based on how the test case is to be vectorized */
function vectorizeToExpression(vectorize: undefined | 2 | 3 | 4): ShaderBuilder {
return vectorize === undefined ? unary('bool') : unary(`vec${vectorize}<bool>`);
@@ -95,7 +31,14 @@ Identity operation
)
.fn(async t => {
const cases = await d.get('bool');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeBool, t.params, cases);
+ await run(
+ t,
+ vectorizeToExpression(t.params.vectorize),
+ [Type.bool],
+ Type.bool,
+ t.params,
+ cases
+ );
});
g.test('u32')
@@ -113,7 +56,7 @@ The result is false if e is 0, and true otherwise.
)
.fn(async t => {
const cases = await d.get('u32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeBool, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.u32], Type.bool, t.params, cases);
});
g.test('i32')
@@ -131,7 +74,7 @@ The result is false if e is 0, and true otherwise.
)
.fn(async t => {
const cases = await d.get('i32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeBool, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.i32], Type.bool, t.params, cases);
});
g.test('f32')
@@ -149,7 +92,7 @@ The result is false if e is 0.0 or -0.0, and true otherwise.
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeBool, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f32], Type.bool, t.params, cases);
});
g.test('f16')
@@ -170,5 +113,5 @@ The result is false if e is 0.0 or -0.0, and true otherwise.
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeBool, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f16], Type.bool, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_logical.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_logical.spec.ts
index 01eaaab43a..58d4b9fc0f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_logical.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/bool_logical.spec.ts
@@ -4,7 +4,7 @@ Execution Tests for the boolean unary logical expression operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { bool, TypeBool } from '../../../../util/conversion.js';
+import { bool, Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
import { unary } from './unary.js';
@@ -29,5 +29,5 @@ Logical negation. The result is true when e is false and false when e is true. C
{ input: bool(false), expected: bool(true) },
];
- await run(t, unary('!'), [TypeBool], TypeBool, t.params, cases);
+ await run(t, unary('!'), [Type.bool], Type.bool, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.cache.ts
new file mode 100644
index 0000000000..7d9ee35eec
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.cache.ts
@@ -0,0 +1,13 @@
+import { FP } from '../../../../util/floating_point.js';
+import { scalarF16Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/f16_arithmetic', {
+ negation: () => {
+ return FP.f16.generateScalarToIntervalCases(
+ scalarF16Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }),
+ 'unfiltered',
+ FP.f16.negationInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.ts
index 83d7579c07..813bbf7a64 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_arithmetic.spec.ts
@@ -4,26 +4,14 @@ 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 { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
+import { d } from './f16_arithmetic.cache.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(
@@ -40,5 +28,5 @@ Accuracy: Correctly rounded
})
.fn(async t => {
const cases = await d.get('negation');
- await run(t, unary('-'), [TypeF16], TypeF16, t.params, cases);
+ await run(t, unary('-'), [Type.f16], Type.f16, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.cache.ts
new file mode 100644
index 0000000000..bb0eb091dd
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.cache.ts
@@ -0,0 +1,135 @@
+import { abstractInt, bool, f16, i32, u32 } from '../../../../util/conversion.js';
+import { FP, FPInterval } from '../../../../util/floating_point.js';
+import { fullI32Range, fullI64Range, fullU32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+const f16FiniteRangeInterval = new FPInterval(
+ 'f16',
+ FP.f16.constants().negative.min,
+ FP.f16.constants().positive.max
+);
+
+// Cases: f32_matCxR_[non_]const
+// Note that f32 values may be not exactly representable in f16 and/or out of range.
+const f32_mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`f32_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateMatrixToMatrixCases(
+ FP.f32.sparseMatrixRange(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.correctlyRoundedMatrix
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: f16_matCxR_[non_]const
+const f16_mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`f16_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ // Input matrix is of f16 types, use f16.generateMatrixToMatrixCases.
+ return FP.f16.generateMatrixToMatrixCases(
+ FP.f16.sparseMatrixRange(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f16.correctlyRoundedMatrix
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: abstract_float_matCxR
+// Note that abstract float values may be not exactly representable in f16
+// and/or out of range.
+const abstract_float_mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).map(rows => ({
+ [`abstract_float_mat${cols}x${rows}`]: () => {
+ return FP.abstract.generateMatrixToMatrixCases(
+ FP.abstract.sparseMatrixRange(cols, rows),
+ 'finite',
+ FP.f16.correctlyRoundedMatrix
+ );
+ },
+ }))
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('unary/f16_conversion', {
+ bool: () => {
+ return [
+ { input: bool(true), expected: f16(1.0) },
+ { input: bool(false), expected: f16(0.0) },
+ ];
+ },
+ u32_non_const: () => {
+ return [...fullU32Range(), 65504].map(u => {
+ return { input: u32(u), expected: FP.f16.correctlyRoundedInterval(u) };
+ });
+ },
+ u32_const: () => {
+ return [...fullU32Range(), 65504]
+ .filter(v => f16FiniteRangeInterval.contains(v))
+ .map(u => {
+ return { input: u32(u), expected: FP.f16.correctlyRoundedInterval(u) };
+ });
+ },
+ i32_non_const: () => {
+ return [...fullI32Range(), 65504, -65504].map(i => {
+ return { input: i32(i), expected: FP.f16.correctlyRoundedInterval(i) };
+ });
+ },
+ i32_const: () => {
+ return [...fullI32Range(), 65504, -65504]
+ .filter(v => f16FiniteRangeInterval.contains(v))
+ .map(i => {
+ return { input: i32(i), expected: FP.f16.correctlyRoundedInterval(i) };
+ });
+ },
+ abstract_int: () => {
+ return [...fullI64Range(), 65504n, -65504n]
+ .filter(v => f16FiniteRangeInterval.contains(Number(v)))
+ .map(i => {
+ return { input: abstractInt(i), expected: FP.f16.correctlyRoundedInterval(Number(i)) };
+ });
+ },
+ // Note that f32 values may be not exactly representable in f16 and/or out of range.
+ f32_non_const: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [...FP.f32.scalarRange(), 65535.996, -65535.996],
+ 'unfiltered',
+ FP.f16.correctlyRoundedInterval
+ );
+ },
+ f32_const: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ [...FP.f32.scalarRange(), 65535.996, -65535.996],
+ 'finite',
+ FP.f16.correctlyRoundedInterval
+ );
+ },
+ // Note that abstract float values may be not exactly representable in f16.
+ abstract_float: () => {
+ return FP.abstract.generateScalarToIntervalCases(
+ [...FP.abstract.scalarRange(), 65535.996, -65535.996],
+ 'finite',
+ FP.f16.correctlyRoundedInterval
+ );
+ },
+ // All f16 values are exactly representable in f16.
+ f16: () => {
+ return FP.f16.scalarRange().map(f => {
+ return { input: f16(f), expected: FP.f16.correctlyRoundedInterval(f) };
+ });
+ },
+ ...f32_mat_cases,
+ ...f16_mat_cases,
+ ...abstract_float_mat_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.spec.ts
index 9eb84f0270..92bd9c6a07 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f16_conversion.spec.ts
@@ -4,132 +4,14 @@ Execution Tests for the f32 conversion operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import {
- bool,
- f16,
- i32,
- TypeBool,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeMat,
- TypeU32,
- u32,
-} from '../../../../util/conversion.js';
-import { FP, FPInterval } from '../../../../util/floating_point.js';
-import {
- fullF32Range,
- fullF16Range,
- fullI32Range,
- fullU32Range,
- sparseMatrixF32Range,
- sparseMatrixF16Range,
-} from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, run, ShaderBuilder } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { ShaderBuilder, allInputSources, run, onlyConstInputSource } from '../expression.js';
+import { d } from './f16_conversion.cache.js';
import { unary } from './unary.js';
export const g = makeTestGroup(GPUTest);
-const f16FiniteRangeInterval = new FPInterval(
- 'f32',
- FP.f16.constants().negative.min,
- FP.f16.constants().positive.max
-);
-
-// Cases: f32_matCxR_[non_]const
-// Note that f32 values may be not exactly representable in f16 and/or out of range.
-const f32_mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`f32_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateMatrixToMatrixCases(
- sparseMatrixF32Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.correctlyRoundedMatrix
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_matCxR_[non_]const
-const f16_mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`f16_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- // Input matrix is of f16 types, use f16.generateMatrixToMatrixCases.
- return FP.f16.generateMatrixToMatrixCases(
- sparseMatrixF16Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f16.correctlyRoundedMatrix
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('unary/f16_conversion', {
- bool: () => {
- return [
- { input: bool(true), expected: f16(1.0) },
- { input: bool(false), expected: f16(0.0) },
- ];
- },
- u32_non_const: () => {
- return [...fullU32Range(), 65504].map(u => {
- return { input: u32(u), expected: FP.f16.correctlyRoundedInterval(u) };
- });
- },
- u32_const: () => {
- return [...fullU32Range(), 65504]
- .filter(v => f16FiniteRangeInterval.contains(v))
- .map(u => {
- return { input: u32(u), expected: FP.f16.correctlyRoundedInterval(u) };
- });
- },
- i32_non_const: () => {
- return [...fullI32Range(), 65504, -65504].map(i => {
- return { input: i32(i), expected: FP.f16.correctlyRoundedInterval(i) };
- });
- },
- i32_const: () => {
- return [...fullI32Range(), 65504, -65504]
- .filter(v => f16FiniteRangeInterval.contains(v))
- .map(i => {
- return { input: i32(i), expected: FP.f16.correctlyRoundedInterval(i) };
- });
- },
- // Note that f32 values may be not exactly representable in f16 and/or out of range.
- f32_non_const: () => {
- return FP.f32.generateScalarToIntervalCases(
- [...fullF32Range(), 65535.996, -65535.996],
- 'unfiltered',
- FP.f16.correctlyRoundedInterval
- );
- },
- f32_const: () => {
- return FP.f32.generateScalarToIntervalCases(
- [...fullF32Range(), 65535.996, -65535.996],
- 'finite',
- FP.f16.correctlyRoundedInterval
- );
- },
- // All f16 values are exactly representable in f16.
- f16: () => {
- return fullF16Range().map(f => {
- return { input: f16(f), expected: FP.f16.correctlyRoundedInterval(f) };
- });
- },
- ...f32_mat_cases,
- ...f16_mat_cases,
-});
-
/** Generate a ShaderBuilder based on how the test case is to be vectorized */
function vectorizeToExpression(vectorize: undefined | 2 | 3 | 4): ShaderBuilder {
return vectorize === undefined ? unary('f16') : unary(`vec${vectorize}<f16>`);
@@ -157,7 +39,7 @@ The result is 1.0 if e is true and 0.0 otherwise
})
.fn(async t => {
const cases = await d.get('bool');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeF16, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.bool], Type.f16, t.params, cases);
});
g.test('u32')
@@ -177,7 +59,7 @@ Converted to f16, +/-Inf if out of range
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'u32_const' : 'u32_non_const');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeF16, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.u32], Type.f16, t.params, cases);
});
g.test('i32')
@@ -197,7 +79,36 @@ Converted to f16, +/-Inf if out of range
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'i32_const' : 'i32_non_const');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeF16, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.i32], Type.f16, t.params, cases);
+ });
+
+g.test('abstract_int')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(
+ `
+f16(e), where e is an AbstractInt
+
+Converted to f16, +/-Inf if out of range
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(async t => {
+ const cases = await d.get('abstract_int');
+ await run(
+ t,
+ vectorizeToExpression(t.params.vectorize),
+ [Type.abstractInt],
+ Type.f16,
+ t.params,
+ cases
+ );
});
g.test('f32')
@@ -217,7 +128,7 @@ Correctly rounded to f16
})
.fn(async t => {
const cases = await d.get(t.params.inputSource === 'const' ? 'f32_const' : 'f32_non_const');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeF16, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f32], Type.f16, t.params, cases);
});
g.test('f32_mat')
@@ -243,8 +154,8 @@ g.test('f32_mat')
await run(
t,
matrixExperession(cols, rows),
- [TypeMat(cols, rows, TypeF32)],
- TypeMat(cols, rows, TypeF16),
+ [Type.mat(cols, rows, Type.f32)],
+ Type.mat(cols, rows, Type.f16),
t.params,
cases
);
@@ -267,7 +178,7 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeF16, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f16], Type.f16, t.params, cases);
});
g.test('f16_mat')
@@ -293,8 +204,63 @@ g.test('f16_mat')
await run(
t,
matrixExperession(cols, rows),
- [TypeMat(cols, rows, TypeF16)],
- TypeMat(cols, rows, TypeF16),
+ [Type.mat(cols, rows, Type.f16)],
+ Type.mat(cols, rows, Type.f16),
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(
+ `
+f16(e), where e is an AbstractFloat
+
+Correctly rounded to f16
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(async t => {
+ const cases = await d.get('abstract_float');
+ await run(
+ t,
+ vectorizeToExpression(t.params.vectorize),
+ [Type.abstractFloat],
+ Type.f16,
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_float_mat')
+ .specURL('https://www.w3.org/TR/WGSL/#matrix-builtin-functions')
+ .desc(`AbstractFloat matrix to f16 matrix tests`)
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('cols', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(async t => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const cases = await d.get(`abstract_float_mat${cols}x${rows}`);
+ await run(
+ t,
+ matrixExperession(cols, rows),
+ [Type.mat(cols, rows, Type.abstractFloat)],
+ Type.mat(cols, rows, Type.f16),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.cache.ts
new file mode 100644
index 0000000000..b23ed3216b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.cache.ts
@@ -0,0 +1,13 @@
+import { FP } from '../../../../util/floating_point.js';
+import { scalarF32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/f32_arithmetic', {
+ negation: () => {
+ return FP.f32.generateScalarToIntervalCases(
+ scalarF32Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }),
+ 'unfiltered',
+ FP.f32.negationInterval
+ );
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.spec.ts
index f53cff46d8..3bb48705e8 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_arithmetic.spec.ts
@@ -4,26 +4,14 @@ Execution Tests for the f32 arithmetic unary expression operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { TypeF32 } from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import { fullF32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
+import { d } from './f32_arithmetic.cache.js';
import { unary } from './unary.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unary/f32_arithmetic', {
- negation: () => {
- return FP.f32.generateScalarToIntervalCases(
- fullF32Range({ neg_norm: 250, neg_sub: 20, pos_sub: 20, pos_norm: 250 }),
- 'unfiltered',
- FP.f32.negationInterval
- );
- },
-});
-
g.test('negation')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -37,5 +25,5 @@ Accuracy: Correctly rounded
)
.fn(async t => {
const cases = await d.get('negation');
- await run(t, unary('-'), [TypeF32], TypeF32, t.params, cases);
+ await run(t, unary('-'), [Type.f32], Type.f32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.cache.ts
new file mode 100644
index 0000000000..f61435f07c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.cache.ts
@@ -0,0 +1,79 @@
+import { bool, f16, f32, i32, u32 } from '../../../../util/conversion.js';
+import { FP } from '../../../../util/floating_point.js';
+import {
+ fullI32Range,
+ fullU32Range,
+ scalarF16Range,
+ scalarF32Range,
+ sparseMatrixF16Range,
+ sparseMatrixF32Range,
+} from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+// Cases: f32_matCxR_[non_]const
+const f32_mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`f32_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ return FP.f32.generateMatrixToMatrixCases(
+ sparseMatrixF32Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.correctlyRoundedMatrix
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+// Cases: f16_matCxR_[non_]const
+// Note that all f16 values are exactly representable in f32.
+const f16_mat_cases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ ([true, false] as const).map(nonConst => ({
+ [`f16_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
+ // Input matrix is of f16 types, use f16.generateMatrixToMatrixCases.
+ return FP.f16.generateMatrixToMatrixCases(
+ sparseMatrixF16Range(cols, rows),
+ nonConst ? 'unfiltered' : 'finite',
+ FP.f32.correctlyRoundedMatrix
+ );
+ },
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+export const d = makeCaseCache('unary/f32_conversion', {
+ bool: () => {
+ return [
+ { input: bool(true), expected: f32(1.0) },
+ { input: bool(false), expected: f32(0.0) },
+ ];
+ },
+ u32: () => {
+ return fullU32Range().map(u => {
+ return { input: u32(u), expected: FP.f32.correctlyRoundedInterval(u) };
+ });
+ },
+ i32: () => {
+ return fullI32Range().map(i => {
+ return { input: i32(i), expected: FP.f32.correctlyRoundedInterval(i) };
+ });
+ },
+ f32: () => {
+ return scalarF32Range().map(f => {
+ return { input: f32(f), expected: FP.f32.correctlyRoundedInterval(f) };
+ });
+ },
+ // All f16 values are exactly representable in f32.
+ f16: () => {
+ return scalarF16Range().map(f => {
+ return { input: f16(f), expected: FP.f32.correctlyRoundedInterval(f) };
+ });
+ },
+ ...f32_mat_cases,
+ ...f16_mat_cases,
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.spec.ts
index 223b13c2d5..464fdee44e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/f32_conversion.spec.ts
@@ -4,103 +4,14 @@ Execution Tests for the f32 conversion operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import {
- bool,
- f32,
- f16,
- i32,
- TypeBool,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeMat,
- TypeU32,
- u32,
-} from '../../../../util/conversion.js';
-import { FP } from '../../../../util/floating_point.js';
-import {
- fullF32Range,
- fullF16Range,
- fullI32Range,
- fullU32Range,
- sparseMatrixF32Range,
- sparseMatrixF16Range,
-} from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, run, ShaderBuilder } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { ShaderBuilder, allInputSources, run } from '../expression.js';
+import { d } from './f32_conversion.cache.js';
import { unary } from './unary.js';
export const g = makeTestGroup(GPUTest);
-// Cases: f32_matCxR_[non_]const
-const f32_mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`f32_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- return FP.f32.generateMatrixToMatrixCases(
- sparseMatrixF32Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.correctlyRoundedMatrix
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-// Cases: f16_matCxR_[non_]const
-// Note that all f16 values are exactly representable in f32.
-const f16_mat_cases = ([2, 3, 4] as const)
- .flatMap(cols =>
- ([2, 3, 4] as const).flatMap(rows =>
- ([true, false] as const).map(nonConst => ({
- [`f16_mat${cols}x${rows}_${nonConst ? 'non_const' : 'const'}`]: () => {
- // Input matrix is of f16 types, use f16.generateMatrixToMatrixCases.
- return FP.f16.generateMatrixToMatrixCases(
- sparseMatrixF16Range(cols, rows),
- nonConst ? 'unfiltered' : 'finite',
- FP.f32.correctlyRoundedMatrix
- );
- },
- }))
- )
- )
- .reduce((a, b) => ({ ...a, ...b }), {});
-
-export const d = makeCaseCache('unary/f32_conversion', {
- bool: () => {
- return [
- { input: bool(true), expected: f32(1.0) },
- { input: bool(false), expected: f32(0.0) },
- ];
- },
- u32: () => {
- return fullU32Range().map(u => {
- return { input: u32(u), expected: FP.f32.correctlyRoundedInterval(u) };
- });
- },
- i32: () => {
- return fullI32Range().map(i => {
- return { input: i32(i), expected: FP.f32.correctlyRoundedInterval(i) };
- });
- },
- f32: () => {
- return fullF32Range().map(f => {
- return { input: f32(f), expected: FP.f32.correctlyRoundedInterval(f) };
- });
- },
- // All f16 values are exactly representable in f32.
- f16: () => {
- return fullF16Range().map(f => {
- return { input: f16(f), expected: FP.f32.correctlyRoundedInterval(f) };
- });
- },
- ...f32_mat_cases,
- ...f16_mat_cases,
-});
-
/** Generate a ShaderBuilder based on how the test case is to be vectorized */
function vectorizeToExpression(vectorize: undefined | 2 | 3 | 4): ShaderBuilder {
return vectorize === undefined ? unary('f32') : unary(`vec${vectorize}<f32>`);
@@ -125,7 +36,7 @@ The result is 1.0 if e is true and 0.0 otherwise
)
.fn(async t => {
const cases = await d.get('bool');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeF32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.bool], Type.f32, t.params, cases);
});
g.test('u32')
@@ -142,7 +53,7 @@ Converted to f32
)
.fn(async t => {
const cases = await d.get('u32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeF32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.u32], Type.f32, t.params, cases);
});
g.test('i32')
@@ -159,7 +70,7 @@ Converted to f32
)
.fn(async t => {
const cases = await d.get('i32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeF32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.i32], Type.f32, t.params, cases);
});
g.test('f32')
@@ -176,7 +87,7 @@ Identity operation
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeF32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f32], Type.f32, t.params, cases);
});
g.test('f32_mat')
@@ -199,8 +110,8 @@ g.test('f32_mat')
await run(
t,
matrixExperession(cols, rows),
- [TypeMat(cols, rows, TypeF32)],
- TypeMat(cols, rows, TypeF32),
+ [Type.mat(cols, rows, Type.f32)],
+ Type.mat(cols, rows, Type.f32),
t.params,
cases
);
@@ -223,7 +134,7 @@ g.test('f16')
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeF32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f16], Type.f32, t.params, cases);
});
g.test('f16_mat')
@@ -249,8 +160,8 @@ g.test('f16_mat')
await run(
t,
matrixExperession(cols, rows),
- [TypeMat(cols, rows, TypeF16)],
- TypeMat(cols, rows, TypeF32),
+ [Type.mat(cols, rows, Type.f16)],
+ Type.mat(cols, rows, Type.f32),
t.params,
cases
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.cache.ts
new file mode 100644
index 0000000000..b7206bcf45
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.cache.ts
@@ -0,0 +1,11 @@
+import { i32 } from '../../../../util/conversion.js';
+import { fullI32Range } from '../../../../util/math.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/i32_arithmetic', {
+ negation: () => {
+ return fullI32Range().map(e => {
+ return { input: i32(e), expected: i32(-e) };
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.spec.ts
index 14519b8967..a7d16a96cd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_arithmetic.spec.ts
@@ -4,23 +4,14 @@ Execution Tests for the i32 arithmetic unary expression operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { i32, TypeI32 } from '../../../../util/conversion.js';
-import { fullI32Range } from '../../../../util/math.js';
-import { makeCaseCache } from '../case_cache.js';
+import { Type } from '../../../../util/conversion.js';
import { allInputSources, run } from '../expression.js';
+import { d } from './i32_arithmetic.cache.js';
import { unary } from './unary.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unary/i32_arithmetic', {
- negation: () => {
- return fullI32Range().map(e => {
- return { input: i32(e), expected: i32(-e) };
- });
- },
-});
-
g.test('negation')
.specURL('https://www.w3.org/TR/WGSL/#floating-point-evaluation')
.desc(
@@ -33,5 +24,5 @@ Expression: -x
)
.fn(async t => {
const cases = await d.get('negation');
- await run(t, unary('-'), [TypeI32], TypeI32, t.params, cases);
+ await run(t, unary('-'), [Type.i32], Type.i32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_complement.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_complement.spec.ts
index e8bda51b51..01e5728d70 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_complement.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_complement.spec.ts
@@ -4,23 +4,14 @@ Execution Tests for the i32 bitwise complement operation
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { i32, TypeI32 } from '../../../../util/conversion.js';
+import { i32, Type } from '../../../../util/conversion.js';
import { fullI32Range } 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/i32_complement', {
- complement: () => {
- return fullI32Range().map(e => {
- return { input: i32(e), expected: i32(~e) };
- });
- },
-});
-
g.test('i32_complement')
.specURL('https://www.w3.org/TR/WGSL/#bit-expr')
.desc(
@@ -32,6 +23,8 @@ Expression: ~x
u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
- const cases = await d.get('complement');
- await run(t, unary('~'), [TypeI32], TypeI32, t.params, cases);
+ const cases = fullI32Range().map(e => {
+ return { input: i32(e), expected: i32(~e) };
+ });
+ await run(t, unary('~'), [Type.i32], Type.i32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.cache.ts
new file mode 100644
index 0000000000..1c2d01548d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.cache.ts
@@ -0,0 +1,116 @@
+import { kValue } from '../../../../util/constants.js';
+import {
+ abstractFloat,
+ abstractInt,
+ bool,
+ f16,
+ f32,
+ i32,
+ u32,
+} from '../../../../util/conversion.js';
+import {
+ fullI32Range,
+ fullU32Range,
+ quantizeToF16,
+ quantizeToF32,
+ scalarF16Range,
+ scalarF32Range,
+ scalarF64Range,
+} from '../../../../util/math.js';
+import { reinterpretU32AsI32 } from '../../../../util/reinterpret.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/i32_conversion', {
+ bool: () => {
+ return [
+ { input: bool(true), expected: i32(1) },
+ { input: bool(false), expected: i32(0) },
+ ];
+ },
+ abstractInt: () => {
+ return fullI32Range().map(i => {
+ return { input: abstractInt(BigInt(i)), expected: i32(i) };
+ });
+ },
+ u32: () => {
+ return fullU32Range().map(u => {
+ return { input: u32(u), expected: i32(reinterpretU32AsI32(u)) };
+ });
+ },
+ i32: () => {
+ return fullI32Range().map(i => {
+ return { input: i32(i), expected: i32(i) };
+ });
+ },
+ abstractFloat: () => {
+ return scalarF64Range().map(f => {
+ // Handles zeros and subnormals
+ if (Math.abs(f) < 1.0) {
+ return { input: abstractFloat(f), expected: i32(0) };
+ }
+
+ if (f <= kValue.i32.negative.min) {
+ return { input: abstractFloat(f), expected: i32(kValue.i32.negative.min) };
+ }
+
+ if (f >= kValue.i32.positive.max) {
+ return { input: abstractFloat(f), expected: i32(kValue.i32.positive.max) };
+ }
+
+ // All i32s are representable as f64, and both AbstractFloat and number
+ // are f64 internally, so there is no need for special casing like f32 and
+ // f16 below.
+ return { input: abstractFloat(f), expected: i32(Math.trunc(f)) };
+ });
+ },
+ f32: () => {
+ return scalarF32Range().map(f => {
+ // Handles zeros and subnormals
+ if (Math.abs(f) < 1.0) {
+ return { input: f32(f), expected: i32(0) };
+ }
+
+ if (f <= kValue.i32.negative.min) {
+ return { input: f32(f), expected: i32(kValue.i32.negative.min) };
+ }
+
+ if (f >= kValue.i32.positive.max) {
+ return { input: f32(f), expected: i32(kValue.i32.positive.max) };
+ }
+
+ // All f32 no larger than 2^24 has a precise interger part and a fractional part, just need
+ // to trunc towards 0 for the result integer.
+ if (Math.abs(f) <= 2 ** 24) {
+ return { input: f32(f), expected: i32(Math.trunc(f)) };
+ }
+
+ // All f32s between 2 ** 24 and kValue.i32.negative.min/.positive.max are
+ // integers, so in theory one could use them directly, expect that number
+ // is actually f64 internally, so they need to be quantized to f32 first.
+ // Cannot just use trunc here, since that might produce a i32 value that
+ // is precise in f64, but not in f32.
+ return { input: f32(f), expected: i32(quantizeToF32(f)) };
+ });
+ },
+ f16: () => {
+ // Note that finite f16 values are always in range of i32.
+ return scalarF16Range().map(f => {
+ // Handles zeros and subnormals
+ if (Math.abs(f) < 1.0) {
+ return { input: f16(f), expected: i32(0) };
+ }
+
+ // All f16 no larger than <= 2^12 has a precise interger part and a fractional part, just need
+ // to trunc towards 0 for the result integer.
+ if (Math.abs(f) <= 2 ** 12) {
+ return { input: f16(f), expected: i32(Math.trunc(f)) };
+ }
+
+ // All f16s larger than 2 ** 12 are integers, so in theory one could use them directly, expect
+ // that number is actually f64 internally, so they need to be quantized to f16 first.
+ // Cannot just use trunc here, since that might produce a i32 value that is precise in f64,
+ // but not in f16.
+ return { input: f16(f), expected: i32(quantizeToF16(f)) };
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.spec.ts
index a77aa0e4d3..b47ffe0d07 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/i32_conversion.spec.ts
@@ -4,104 +4,14 @@ Execution Tests for the i32 conversion operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { kValue } from '../../../../util/constants.js';
-import {
- bool,
- f32,
- f16,
- i32,
- TypeBool,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeU32,
- u32,
-} from '../../../../util/conversion.js';
-import {
- fullF32Range,
- fullF16Range,
- fullI32Range,
- fullU32Range,
- quantizeToF32,
- quantizeToF16,
-} from '../../../../util/math.js';
-import { reinterpretU32AsI32 } from '../../../../util/reinterpret.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, run, ShaderBuilder } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { ShaderBuilder, allInputSources, run, onlyConstInputSource } from '../expression.js';
+import { d } from './i32_conversion.cache.js';
import { unary } from './unary.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unary/i32_conversion', {
- bool: () => {
- return [
- { input: bool(true), expected: i32(1) },
- { input: bool(false), expected: i32(0) },
- ];
- },
- u32: () => {
- return fullU32Range().map(u => {
- return { input: u32(u), expected: i32(reinterpretU32AsI32(u)) };
- });
- },
- i32: () => {
- return fullI32Range().map(i => {
- return { input: i32(i), expected: i32(i) };
- });
- },
- f32: () => {
- return fullF32Range().map(f => {
- // Handles zeros and subnormals
- if (Math.abs(f) < 1.0) {
- return { input: f32(f), expected: i32(0) };
- }
-
- if (f <= kValue.i32.negative.min) {
- return { input: f32(f), expected: i32(kValue.i32.negative.min) };
- }
-
- if (f >= kValue.i32.positive.max) {
- return { input: f32(f), expected: i32(kValue.i32.positive.max) };
- }
-
- // All f32 no larger than 2^24 has a precise interger part and a fractional part, just need
- // to trunc towards 0 for the result integer.
- if (Math.abs(f) <= 2 ** 24) {
- return { input: f32(f), expected: i32(Math.trunc(f)) };
- }
-
- // All f32s between 2 ** 24 and kValue.i32.negative.min/.positive.max are
- // integers, so in theory one could use them directly, expect that number
- // is actually f64 internally, so they need to be quantized to f32 first.
- // Cannot just use trunc here, since that might produce a i32 value that
- // is precise in f64, but not in f32.
- return { input: f32(f), expected: i32(quantizeToF32(f)) };
- });
- },
- f16: () => {
- // Note that finite f16 values are always in range of i32.
- return fullF16Range().map(f => {
- // Handles zeros and subnormals
- if (Math.abs(f) < 1.0) {
- return { input: f16(f), expected: i32(0) };
- }
-
- // All f16 no larger than <= 2^12 has a precise interger part and a fractional part, just need
- // to trunc towards 0 for the result integer.
- if (Math.abs(f) <= 2 ** 12) {
- return { input: f16(f), expected: i32(Math.trunc(f)) };
- }
-
- // All f16s larger than 2 ** 12 are integers, so in theory one could use them directly, expect
- // that number is actually f64 internally, so they need to be quantized to f16 first.
- // Cannot just use trunc here, since that might produce a i32 value that is precise in f64,
- // but not in f16.
- return { input: f16(f), expected: i32(quantizeToF16(f)) };
- });
- },
-});
-
/** Generate a ShaderBuilder based on how the test case is to be vectorized */
function vectorizeToExpression(vectorize: undefined | 2 | 3 | 4): ShaderBuilder {
return vectorize === undefined ? unary('i32') : unary(`vec${vectorize}<i32>`);
@@ -121,7 +31,7 @@ The result is 1u if e is true and 0u otherwise
)
.fn(async t => {
const cases = await d.get('bool');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeI32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.bool], Type.i32, t.params, cases);
});
g.test('u32')
@@ -138,7 +48,7 @@ Reinterpretation of bits
)
.fn(async t => {
const cases = await d.get('u32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeI32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.u32], Type.i32, t.params, cases);
});
g.test('i32')
@@ -155,7 +65,7 @@ Identity operation
)
.fn(async t => {
const cases = await d.get('i32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeI32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.i32], Type.i32, t.params, cases);
});
g.test('f32')
@@ -172,7 +82,7 @@ e is converted to i32, rounding towards zero
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeI32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f32], Type.i32, t.params, cases);
});
g.test('f16')
@@ -192,5 +102,57 @@ e is converted to u32, rounding towards zero
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeI32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f16], Type.i32, t.params, cases);
+ });
+
+g.test('abstract_int')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(
+ `
+i32(e), where e is an AbstractInt
+
+Identity operation if e is in bounds for i32, otherwise shader creation error
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstractInt');
+ await run(
+ t,
+ vectorizeToExpression(t.params.vectorize),
+ [Type.abstractInt],
+ Type.i32,
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(
+ `
+i32(e), where e is an AbstractFloat
+
+e is converted to i32, rounding towards zero
+`
+ )
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstractFloat');
+ await run(
+ t,
+ vectorizeToExpression(t.params.vectorize),
+ [Type.abstractFloat],
+ Type.i32,
+ t.params,
+ cases
+ );
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_complement.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_complement.spec.ts
index 446e0918bd..74251a32c6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_complement.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_complement.spec.ts
@@ -4,23 +4,14 @@ Execution Tests for the u32 bitwise complement operation
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { u32, TypeU32 } from '../../../../util/conversion.js';
+import { Type, u32 } from '../../../../util/conversion.js';
import { fullU32Range } 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/u32_complement', {
- complement: () => {
- return fullU32Range().map(e => {
- return { input: u32(e), expected: u32(~e) };
- });
- },
-});
-
g.test('u32_complement')
.specURL('https://www.w3.org/TR/WGSL/#bit-expr')
.desc(
@@ -32,6 +23,8 @@ Expression: ~x
u.combine('inputSource', allInputSources).combine('vectorize', [undefined, 2, 3, 4] as const)
)
.fn(async t => {
- const cases = await d.get('complement');
- await run(t, unary('~'), [TypeU32], TypeU32, t.params, cases);
+ const cases = fullU32Range().map(e => {
+ return { input: u32(e), expected: u32(~e) };
+ });
+ await run(t, unary('~'), [Type.u32], Type.u32, t.params, cases);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.cache.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.cache.ts
new file mode 100644
index 0000000000..1ef307810f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.cache.ts
@@ -0,0 +1,107 @@
+import { kValue } from '../../../../util/constants.js';
+import {
+ abstractFloat,
+ abstractInt,
+ bool,
+ f16,
+ f32,
+ i32,
+ u32,
+} from '../../../../util/conversion.js';
+import {
+ fullI32Range,
+ fullU32Range,
+ quantizeToF16,
+ quantizeToF32,
+ scalarF16Range,
+ scalarF32Range,
+ scalarF64Range,
+} from '../../../../util/math.js';
+import { reinterpretI32AsU32 } from '../../../../util/reinterpret.js';
+import { makeCaseCache } from '../case_cache.js';
+
+export const d = makeCaseCache('unary/u32_conversion', {
+ bool: () => {
+ return [
+ { input: bool(true), expected: u32(1) },
+ { input: bool(false), expected: u32(0) },
+ ];
+ },
+ abstractInt: () => {
+ return fullU32Range().map(u => {
+ return { input: abstractInt(BigInt(u)), expected: u32(u) };
+ });
+ },
+ u32: () => {
+ return fullU32Range().map(u => {
+ return { input: u32(u), expected: u32(u) };
+ });
+ },
+ i32: () => {
+ return fullI32Range().map(i => {
+ return { input: i32(i), expected: u32(reinterpretI32AsU32(i)) };
+ });
+ },
+ abstractFloat: () => {
+ return [...scalarF64Range(), -1].map(f => {
+ // Handles zeros, subnormals, and negatives
+ if (f < 1.0) {
+ return { input: abstractFloat(f), expected: u32(0) };
+ }
+
+ if (f >= kValue.u32.max) {
+ return { input: abstractFloat(f), expected: u32(kValue.u32.max) };
+ }
+
+ // All u32s are representable as f64s and number is a f64 internally, so
+ // no need for special handling like is done for f32 and f16 below.
+ return { input: abstractFloat(f), expected: u32(Math.floor(f)) };
+ });
+ },
+ f32: () => {
+ return scalarF32Range().map(f => {
+ // Handles zeros, subnormals, and negatives
+ if (f < 1.0) {
+ return { input: f32(f), expected: u32(0) };
+ }
+
+ if (f >= kValue.u32.max) {
+ return { input: f32(f), expected: u32(kValue.u32.max) };
+ }
+
+ // All f32 no larger than 2^24 has a precise integer part and a fractional
+ // part, just need to trunc towards 0 for the result integer.
+ if (f <= 2 ** 24) {
+ return { input: f32(f), expected: u32(Math.floor(f)) };
+ }
+
+ // All f32s between 2 ** 24 and kValue.u32.max are integers, so in theory
+ // one could use them directly, expect that number is actually f64
+ // internally, so they need to be quantized to f32 first.
+ // Cannot just use floor here, since that might produce a u32 value that
+ // is precise in f64, but not in f32.
+ return { input: f32(f), expected: u32(quantizeToF32(f)) };
+ });
+ },
+ f16: () => {
+ // Note that all positive finite f16 values are in range of u32.
+ return scalarF16Range().map(f => {
+ // Handles zeros, subnormals, and negatives
+ if (f < 1.0) {
+ return { input: f16(f), expected: u32(0) };
+ }
+
+ // All f16 no larger than <= 2^12 has a precise integer part and a
+ // fractional part, just need to trunc towards 0 for the result integer.
+ if (f <= 2 ** 12) {
+ return { input: f16(f), expected: u32(Math.trunc(f)) };
+ }
+
+ // All f16s larger than 2 ** 12 are integers, so in theory one could use
+ // them directly, expect that number is actually f64 internally, so they
+ // need to be quantized to f16 first.Cannot just use trunc here, since
+ // that might produce a u32 value that is precise in f64, but not in f16.
+ return { input: f16(f), expected: u32(quantizeToF16(f)) };
+ });
+ },
+});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.spec.ts
index 87dc6e7a5d..6d342afffb 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/u32_conversion.spec.ts
@@ -4,100 +4,14 @@ Execution Tests for the u32 conversion operations
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { GPUTest } from '../../../../gpu_test.js';
-import { kValue } from '../../../../util/constants.js';
-import {
- bool,
- f32,
- f16,
- i32,
- TypeBool,
- TypeF32,
- TypeF16,
- TypeI32,
- TypeU32,
- u32,
-} from '../../../../util/conversion.js';
-import {
- fullF32Range,
- fullF16Range,
- fullI32Range,
- fullU32Range,
- quantizeToF32,
- quantizeToF16,
-} from '../../../../util/math.js';
-import { reinterpretI32AsU32 } from '../../../../util/reinterpret.js';
-import { makeCaseCache } from '../case_cache.js';
-import { allInputSources, run, ShaderBuilder } from '../expression.js';
+import { Type } from '../../../../util/conversion.js';
+import { ShaderBuilder, allInputSources, run, onlyConstInputSource } from '../expression.js';
+import { d } from './u32_conversion.cache.js';
import { unary } from './unary.js';
export const g = makeTestGroup(GPUTest);
-export const d = makeCaseCache('unary/u32_conversion', {
- bool: () => {
- return [
- { input: bool(true), expected: u32(1) },
- { input: bool(false), expected: u32(0) },
- ];
- },
- u32: () => {
- return fullU32Range().map(u => {
- return { input: u32(u), expected: u32(u) };
- });
- },
- i32: () => {
- return fullI32Range().map(i => {
- return { input: i32(i), expected: u32(reinterpretI32AsU32(i)) };
- });
- },
- f32: () => {
- return fullF32Range().map(f => {
- // Handles zeros, subnormals, and negatives
- if (f < 1.0) {
- return { input: f32(f), expected: u32(0) };
- }
-
- if (f >= kValue.u32.max) {
- return { input: f32(f), expected: u32(kValue.u32.max) };
- }
-
- // All f32 no larger than 2^24 has a precise interger part and a fractional part, just need
- // to trunc towards 0 for the result integer.
- if (f <= 2 ** 24) {
- return { input: f32(f), expected: u32(Math.floor(f)) };
- }
-
- // All f32s between 2 ** 24 and kValue.u32.max are integers, so in theory
- // one could use them directly, expect that number is actually f64
- // internally, so they need to be quantized to f32 first.
- // Cannot just use floor here, since that might produce a u32 value that
- // is precise in f64, but not in f32.
- return { input: f32(f), expected: u32(quantizeToF32(f)) };
- });
- },
- f16: () => {
- // Note that all positive finite f16 values are in range of u32.
- return fullF16Range().map(f => {
- // Handles zeros, subnormals, and negatives
- if (f < 1.0) {
- return { input: f16(f), expected: u32(0) };
- }
-
- // All f16 no larger than <= 2^12 has a precise interger part and a fractional part, just need
- // to trunc towards 0 for the result integer.
- if (f <= 2 ** 12) {
- return { input: f16(f), expected: u32(Math.trunc(f)) };
- }
-
- // All f16s larger than 2 ** 12 are integers, so in theory one could use them directly, expect
- // that number is actually f64 internally, so they need to be quantized to f16 first.
- // Cannot just use trunc here, since that might produce a u32 value that is precise in f64,
- // but not in f16.
- return { input: f16(f), expected: u32(quantizeToF16(f)) };
- });
- },
-});
-
/** Generate a ShaderBuilder based on how the test case is to be vectorized */
function vectorizeToExpression(vectorize: undefined | 2 | 3 | 4): ShaderBuilder {
return vectorize === undefined ? unary('u32') : unary(`vec${vectorize}<u32>`);
@@ -117,7 +31,7 @@ The result is 1u if e is true and 0u otherwise
)
.fn(async t => {
const cases = await d.get('bool');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeBool], TypeU32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.bool], Type.u32, t.params, cases);
});
g.test('u32')
@@ -134,7 +48,7 @@ Identity operation
)
.fn(async t => {
const cases = await d.get('u32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeU32], TypeU32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.u32], Type.u32, t.params, cases);
});
g.test('i32')
@@ -151,7 +65,7 @@ Reinterpretation of bits
)
.fn(async t => {
const cases = await d.get('i32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeI32], TypeU32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.i32], Type.u32, t.params, cases);
});
g.test('f32')
@@ -168,7 +82,7 @@ e is converted to u32, rounding towards zero
)
.fn(async t => {
const cases = await d.get('f32');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF32], TypeU32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f32], Type.u32, t.params, cases);
});
g.test('f16')
@@ -188,7 +102,7 @@ e is converted to u32, rounding towards zero
})
.fn(async t => {
const cases = await d.get('f16');
- await run(t, vectorizeToExpression(t.params.vectorize), [TypeF16], TypeU32, t.params, cases);
+ await run(t, vectorizeToExpression(t.params.vectorize), [Type.f16], Type.u32, t.params, cases);
});
g.test('abstract_int')
@@ -201,6 +115,44 @@ Identity operation if the e can be represented in u32, otherwise it produces a s
`
)
.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)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstractInt');
+ await run(
+ t,
+ vectorizeToExpression(t.params.vectorize),
+ [Type.abstractInt],
+ Type.u32,
+ t.params,
+ cases
+ );
+ });
+
+g.test('abstract_float')
+ .specURL('https://www.w3.org/TR/WGSL/#value-constructor-builtin-function')
+ .desc(
+ `
+u32(e), where e is an AbstractFloat
+
+e is converted to u32, rounding towards zero
+`
)
- .unimplemented();
+ .params(u =>
+ u
+ .combine('inputSource', onlyConstInputSource)
+ .combine('vectorize', [undefined, 2, 3, 4] as const)
+ )
+ .fn(async t => {
+ const cases = await d.get('abstractFloat');
+ await run(
+ t,
+ vectorizeToExpression(t.params.vectorize),
+ [Type.abstractFloat],
+ Type.u32,
+ t.params,
+ cases
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/unary.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/unary.ts
index 160e465178..109e6c1421 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/unary.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/expression/unary/unary.ts
@@ -1,5 +1,6 @@
import {
abstractFloatShaderBuilder,
+ abstractIntShaderBuilder,
basicExpressionBuilder,
ShaderBuilder,
} from '../expression.js';
@@ -10,6 +11,11 @@ export function unary(op: string): ShaderBuilder {
}
/* @returns a ShaderBuilder that evaluates a prefix unary operation that returns AbstractFloats */
-export function abstractUnary(op: string): ShaderBuilder {
+export function abstractFloatUnary(op: string): ShaderBuilder {
return abstractFloatShaderBuilder(value => `${op}(${value})`);
}
+
+/* @returns a ShaderBuilder that evaluates a prefix unary operation that returns AbstractInts */
+export function abstractIntUnary(op: string): ShaderBuilder {
+ return abstractIntShaderBuilder(value => `${op}(${value})`);
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/call.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/call.spec.ts
index b86134a58c..a146f5742f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/call.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/call.spec.ts
@@ -81,3 +81,116 @@ fn c() {
}`,
}));
});
+
+g.test('arg_eval')
+ .desc('Test that arguments are evaluated left to right')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ a(b(), c(), d());
+ ${f.expect_order(5)}
+`,
+ extra: `
+fn a(p1 : u32, p2 : u32, p3 : u32) {
+ ${f.expect_order(4)}
+}
+fn b() -> u32 {
+ ${f.expect_order(1)}
+ return 0;
+}
+fn c() -> u32 {
+ ${f.expect_order(2)}
+ return 0;
+}
+fn d() -> u32 {
+ ${f.expect_order(3)}
+ return 0;
+}`,
+ }));
+ });
+
+g.test('arg_eval_logical_and')
+ .desc('Test that arguments are evaluated left to right')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ a(b(${f.value(1)}) && c());
+ a(b(${f.value(0)}) && c());
+ ${f.expect_order(6)}
+`,
+ extra: `
+fn a(p : bool) {
+ ${f.expect_order(3, 5)}
+}
+fn b(x : i32) -> bool {
+ ${f.expect_order(1, 4)}
+ return x == 1;
+}
+fn c() -> bool {
+ ${f.expect_order(2)}
+ return true;
+}`,
+ }));
+ });
+
+g.test('arg_eval_logical_or')
+ .desc('Test that arguments are evaluated left to right')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ a(b(${f.value(1)}) || c());
+ a(b(${f.value(0)}) || c());
+ ${f.expect_order(6)}
+`,
+ extra: `
+fn a(p : bool) {
+ ${f.expect_order(3, 5)}
+}
+fn b(x : i32) -> bool {
+ ${f.expect_order(1, 4)}
+ return x == 0;
+}
+fn c() -> bool {
+ ${f.expect_order(2)}
+ return true;
+}`,
+ }));
+ });
+
+g.test('arg_eval_pointers')
+ .desc('Test that arguments are evaluated left to right')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ var x : i32 = ${f.value(0)};
+ ${f.expect_order(0)}
+ _ = c(&x);
+ a(b(&x), c(&x));
+ ${f.expect_order(5)}
+`,
+ extra: `
+fn a(p1 : i32, p2 : i32) {
+ ${f.expect_order(4)}
+}
+fn b(p : ptr<function, i32>) -> i32 {
+ (*p)++;
+ ${f.expect_order(2)}
+ return 0;
+}
+fn c(p : ptr<function, i32>) -> i32 {
+ if (*p == 1) {
+ ${f.expect_order(3)}
+ } else {
+ ${f.expect_order(1)}
+ }
+ return 0;
+}`,
+ }));
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/for.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/for.spec.ts
index 898b8a0e04..5cb7de66c1 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/for.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/for.spec.ts
@@ -269,3 +269,53 @@ g.test('nested_for_continue')
`
);
});
+
+g.test('for_logical_and_condition')
+ .desc('Test flow control for a for-loop with a logical and condition')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ for (var i = ${f.value(0)}; a(i) && b(i); i++) {
+ ${f.expect_order(3, 6)}
+ }
+ ${f.expect_order(8)}
+ `,
+ extra: `
+fn a(i : i32) -> bool {
+ ${f.expect_order(1, 4, 7)}
+ return i < ${f.value(2)};
+}
+fn b(i : i32) -> bool {
+ ${f.expect_order(2, 5)}
+ return i < ${f.value(5)};
+}
+ `,
+ }));
+ });
+
+g.test('for_logical_or_condition')
+ .desc('Test flow control for a for-loop with a logical or condition')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ for (var i = ${f.value(0)}; a(i) || b(i); i++) {
+ ${f.expect_order(2, 4, 7, 10)}
+ }
+ ${f.expect_order(13)}
+ `,
+ extra: `
+fn a(i : i32) -> bool {
+ ${f.expect_order(1, 3, 5, 8, 11)}
+ return i < ${f.value(2)};
+}
+fn b(i : i32) -> bool {
+ ${f.expect_order(6, 9, 12)}
+ return i < ${f.value(4)};
+}
+ `,
+ }));
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/loop.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/loop.spec.ts
index 18d0e5d1ee..6dd99d137d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/loop.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/loop.spec.ts
@@ -123,3 +123,63 @@ g.test('nested_loops')
`
);
});
+
+g.test('loop_break_if_logical_and_condition')
+ .desc('Test flow control for a loop with a logical and break if')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ var i = ${f.value(0)};
+ loop {
+ ${f.expect_order(1, 4, 7)}
+ continuing {
+ i++;
+ break if !(a(i) && b(i));
+ }
+ }
+ ${f.expect_order(9)}
+ `,
+ extra: `
+fn a(i : i32) -> bool {
+ ${f.expect_order(2, 5, 8)}
+ return i < ${f.value(3)};
+}
+fn b(i : i32) -> bool {
+ ${f.expect_order(3, 6)}
+ return i < ${f.value(5)};
+}
+ `,
+ }));
+ });
+
+g.test('loop_break_if_logical_or_condition')
+ .desc('Test flow control for a loop with a logical or break if')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ var i = ${f.value(0)};
+ loop {
+ ${f.expect_order(1, 3, 6, 9)}
+ continuing {
+ i++;
+ break if !(a(i) || b(i));
+ }
+ }
+ ${f.expect_order(12)}
+ `,
+ extra: `
+fn a(i : i32) -> bool {
+ ${f.expect_order(2, 4, 7, 10)}
+ return i < ${f.value(2)};
+}
+fn b(i : i32) -> bool {
+ ${f.expect_order(5, 8, 11)}
+ return i < ${f.value(4)};
+}
+ `,
+ }));
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/switch.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/switch.spec.ts
index e500729614..772cd6a875 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/switch.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/switch.spec.ts
@@ -154,3 +154,36 @@ ${f.expect_order(2)}
`
);
});
+
+g.test('switch_inside_loop_with_continue')
+ .desc('Test that flow control executes correct for a switch calling continue inside a loop')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(
+ t,
+ f => `
+${f.expect_order(0)}
+var i = ${f.value(0)};
+loop {
+ switch (i) {
+ case 1: {
+ ${f.expect_order(4)}
+ continue;
+ }
+ default: {
+ ${f.expect_order(1)}
+ break;
+ }
+ }
+ ${f.expect_order(2)}
+
+ continuing {
+ ${f.expect_order(3, 5)}
+ i++;
+ break if i >= 2;
+ }
+}
+${f.expect_order(6)}
+`
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/while.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/while.spec.ts
index 88ce6838a5..5ce6097a3a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/while.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/flow_control/while.spec.ts
@@ -138,3 +138,57 @@ g.test('while_nested_continue')
`
);
});
+
+g.test('while_logical_and_condition')
+ .desc('Test flow control for a while-loop with a logical and condition')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ var i = ${f.value(0)};
+ while (a(i) && b(i)) {
+ ${f.expect_order(3, 6)}
+ i++;
+ }
+ ${f.expect_order(8)}
+ `,
+ extra: `
+fn a(i : i32) -> bool {
+ ${f.expect_order(1, 4, 7)}
+ return i < ${f.value(2)};
+}
+fn b(i : i32) -> bool {
+ ${f.expect_order(2, 5)}
+ return i < ${f.value(5)};
+}
+ `,
+ }));
+ });
+
+g.test('while_logical_or_condition')
+ .desc('Test flow control for a while-loop with a logical or condition')
+ .params(u => u.combine('preventValueOptimizations', [true, false]))
+ .fn(t => {
+ runFlowControlTest(t, f => ({
+ entrypoint: `
+ ${f.expect_order(0)}
+ var i = ${f.value(0)};
+ while (a(i) || b(i)) {
+ ${f.expect_order(2, 4, 7, 10)}
+ i++;
+ }
+ ${f.expect_order(13)}
+ `,
+ extra: `
+fn a(i : i32) -> bool {
+ ${f.expect_order(1, 3, 5, 8, 11)}
+ return i < ${f.value(2)};
+}
+fn b(i : i32) -> bool {
+ ${f.expect_order(6, 9, 12)}
+ return i < ${f.value(4)};
+}
+ `,
+ }));
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_layout.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_layout.spec.ts
new file mode 100644
index 0000000000..a58a31449f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_layout.spec.ts
@@ -0,0 +1,1059 @@
+export const description = `Test memory layout requirements`;
+
+import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { keysOf } from '../../../common/util/data_tables.js';
+import { iterRange } from '../../../common/util/util.js';
+import { GPUTest } from '../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+interface LayoutCase {
+ type: string;
+ decl?: string;
+ read_assign: string;
+ write_assign: string;
+ offset: number;
+ f16?: boolean;
+ f32?: boolean;
+ skip_uniform?: boolean;
+}
+
+const kLayoutCases: Record<string, LayoutCase> = {
+ vec2u_align8: {
+ type: `S_vec2u_align`,
+ decl: `struct S_vec2u_align {
+ x : u32,
+ y : vec2u,
+ }`,
+ read_assign: `out = in.y[1]`,
+ write_assign: `out.y[1] = in`,
+ offset: 12,
+ },
+ vec3u_align16: {
+ type: `S_vec3u_align`,
+ decl: `struct S_vec3u_align {
+ x : u32,
+ y : vec3u,
+ }`,
+ read_assign: `out = in.y[2]`,
+ write_assign: `out.y[2] = in`,
+ offset: 24,
+ },
+ vec4u_align16: {
+ type: `S_vec4u_align`,
+ decl: `struct S_vec4u_align {
+ x : u32,
+ y : vec4u,
+ }`,
+ read_assign: `out = in.y[0]`,
+ write_assign: `out.y[0] = in`,
+ offset: 16,
+ },
+ struct_align32: {
+ type: `S_align32`,
+ decl: `struct S_align32 {
+ x : u32,
+ @align(32) y : u32,
+ }`,
+ read_assign: `out = in.y;`,
+ write_assign: `out.y = in`,
+ offset: 32,
+ },
+ vec2h_align4: {
+ type: `S_vec2h_align`,
+ decl: `struct S_vec2h_align {
+ x : f16,
+ y : vec2h,
+ }`,
+ read_assign: `out = u32(in.y[0])`,
+ write_assign: `out.y[0] = f16(in)`,
+ offset: 4,
+ f16: true,
+ },
+ vec3h_align8: {
+ type: `S_vec3h_align`,
+ decl: `struct S_vec3h_align {
+ x : f16,
+ y : vec3h,
+ }`,
+ read_assign: `out = u32(in.y[2])`,
+ write_assign: `out.y[2] = f16(in)`,
+ offset: 12,
+ f16: true,
+ },
+ vec4h_align8: {
+ type: `S_vec4h_align`,
+ decl: `struct S_vec4h_align {
+ x : f16,
+ y : vec4h,
+ }`,
+ read_assign: `out = u32(in.y[2])`,
+ write_assign: `out.y[2] = f16(in)`,
+ offset: 12,
+ f16: true,
+ },
+ vec2f_align8: {
+ type: `S_vec2f_align`,
+ decl: `struct S_vec2f_align {
+ x : u32,
+ y : vec2f,
+ }`,
+ read_assign: `out = u32(in.y[1])`,
+ write_assign: `out.y[1] = f32(in)`,
+ offset: 12,
+ f32: true,
+ },
+ vec3f_align16: {
+ type: `S_vec3f_align`,
+ decl: `struct S_vec3f_align {
+ x : u32,
+ y : vec3f,
+ }`,
+ read_assign: `out = u32(in.y[2])`,
+ write_assign: `out.y[2] = f32(in)`,
+ offset: 24,
+ f32: true,
+ },
+ vec4f_align16: {
+ type: `S_vec4f_align`,
+ decl: `struct S_vec4f_align {
+ x : u32,
+ y : vec4f,
+ }`,
+ read_assign: `out = u32(in.y[0])`,
+ write_assign: `out.y[0] = f32(in)`,
+ offset: 16,
+ f32: true,
+ },
+ vec3i_size12: {
+ type: `S_vec3i_size`,
+ decl: `struct S_vec3i_size {
+ x : vec3i,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 12,
+ },
+ vec3h_size6: {
+ type: `S_vec3h_size`,
+ decl: `struct S_vec3h_size {
+ x : vec3h,
+ y : f16,
+ z : f16,
+ }`,
+ read_assign: `out = u32(in.z)`,
+ write_assign: `out.z = f16(in)`,
+ offset: 8,
+ f16: true,
+ },
+ size80: {
+ type: `S_size80`,
+ decl: `struct S_size80 {
+ @size(80) x : u32,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 80,
+ },
+ atomic_align4: {
+ type: `S_atomic_align`,
+ decl: `struct S_atomic_align {
+ x : u32,
+ y : atomic<u32>,
+ }`,
+ read_assign: `out = atomicLoad(&in.y)`,
+ write_assign: `atomicStore(&out.y, in)`,
+ offset: 4,
+ },
+ atomic_size4: {
+ type: `S_atomic_size`,
+ decl: `struct S_atomic_size {
+ x : atomic<u32>,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 4,
+ },
+ mat2x2f_align8: {
+ type: `S_mat2x2f_align`,
+ decl: `struct S_mat2x2f_align {
+ x : u32,
+ y : mat2x2f,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f32(in)`,
+ offset: 8,
+ f32: true,
+ },
+ mat3x2f_align8: {
+ type: `S_mat3x2f_align`,
+ decl: `struct S_mat3x2f_align {
+ x : u32,
+ y : mat3x2f,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f32(in)`,
+ offset: 8,
+ f32: true,
+ },
+ mat4x2f_align8: {
+ type: `S_mat4x2f_align`,
+ decl: `struct S_mat4x2f_align {
+ x : u32,
+ y : mat4x2f,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f32(in)`,
+ offset: 8,
+ f32: true,
+ },
+ mat2x3f_align16: {
+ type: `S_mat2x3f_align`,
+ decl: `struct S_mat2x3f_align {
+ x : u32,
+ y : mat2x3f,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f32(in)`,
+ offset: 16,
+ f32: true,
+ },
+ mat3x3f_align16: {
+ type: `S_mat3x3f_align`,
+ decl: `struct S_mat3x3f_align {
+ x : u32,
+ y : mat3x3f,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f32(in)`,
+ offset: 16,
+ f32: true,
+ },
+ mat4x3f_align16: {
+ type: `S_mat4x3f_align`,
+ decl: `struct S_mat4x3f_align {
+ x : u32,
+ y : mat4x3f,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f32(in)`,
+ offset: 16,
+ f32: true,
+ },
+ mat2x4f_align16: {
+ type: `S_mat2x4f_align`,
+ decl: `struct S_mat2x4f_align {
+ x : u32,
+ y : mat2x4f,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f32(in)`,
+ offset: 16,
+ f32: true,
+ },
+ mat3x4f_align16: {
+ type: `S_mat3x4f_align`,
+ decl: `struct S_mat3x4f_align {
+ x : u32,
+ y : mat3x4f,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f32(in)`,
+ offset: 16,
+ f32: true,
+ },
+ mat4x4f_align16: {
+ type: `S_mat4x4f_align`,
+ decl: `struct S_mat4x4f_align {
+ x : u32,
+ y : mat4x4f,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f32(in)`,
+ offset: 16,
+ f32: true,
+ },
+ mat2x2h_align4: {
+ type: `S_mat2x2h_align`,
+ decl: `struct S_mat2x2h_align {
+ x : u32,
+ y : mat2x2h,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 4,
+ f16: true,
+ },
+ mat3x2h_align4: {
+ type: `S_mat3x2h_align`,
+ decl: `struct S_mat3x2h_align {
+ x : u32,
+ y : mat3x2h,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 4,
+ f16: true,
+ },
+ mat4x2h_align4: {
+ type: `S_mat4x2h_align`,
+ decl: `struct S_mat4x2h_align {
+ x : u32,
+ y : mat4x2h,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 4,
+ f16: true,
+ },
+ mat2x3h_align8: {
+ type: `S_mat2x3h_align`,
+ decl: `struct S_mat2x3h_align {
+ x : u32,
+ y : mat2x3h,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 8,
+ f16: true,
+ },
+ mat3x3h_align8: {
+ type: `S_mat3x3h_align`,
+ decl: `struct S_mat3x3h_align {
+ x : u32,
+ y : mat2x3h,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 8,
+ f16: true,
+ },
+ mat4x3h_align8: {
+ type: `S_mat4x3h_align`,
+ decl: `struct S_mat4x3h_align {
+ x : u32,
+ y : mat4x3h,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 8,
+ f16: true,
+ },
+ mat2x4h_align8: {
+ type: `S_mat2x4h_align`,
+ decl: `struct S_mat2x4h_align {
+ x : u32,
+ y : mat2x4h,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 8,
+ f16: true,
+ },
+ mat3x4h_align8: {
+ type: `S_mat3x4h_align`,
+ decl: `struct S_mat3x4h_align {
+ x : u32,
+ y : mat3x4h,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 8,
+ f16: true,
+ },
+ mat4x4h_align8: {
+ type: `S_mat4x4h_align`,
+ decl: `struct S_mat4x4h_align {
+ x : u32,
+ y : mat4x4h,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 8,
+ f16: true,
+ },
+ mat2x2f_size: {
+ type: `S_mat2x2f_size`,
+ decl: `struct S_mat2x2f_size {
+ x : mat2x2f,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 16,
+ },
+ mat3x2f_size: {
+ type: `S_mat3x2f_size`,
+ decl: `struct S_mat3x2f_size {
+ x : mat3x2f,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 24,
+ },
+ mat4x2f_size: {
+ type: `S_mat4x2f_size`,
+ decl: `struct S_mat4x2f_size {
+ x : mat4x2f,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 32,
+ },
+ mat2x3f_size: {
+ type: `S_mat2x3f_size`,
+ decl: `struct S_mat2x3f_size {
+ x : mat2x3f,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 32,
+ },
+ mat3x3f_size: {
+ type: `S_mat3x3f_size`,
+ decl: `struct S_mat3x3f_size {
+ x : mat3x3f,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 48,
+ },
+ mat4x3f_size: {
+ type: `S_mat4x3f_size`,
+ decl: `struct S_mat4x3f_size {
+ x : mat4x3f,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 64,
+ },
+ mat2x4f_size: {
+ type: `S_mat2x4f_size`,
+ decl: `struct S_mat2x4f_size {
+ x : mat2x4f,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 32,
+ },
+ mat3x4f_size: {
+ type: `S_mat3x4f_size`,
+ decl: `struct S_mat3x4f_size {
+ x : mat3x4f,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 48,
+ },
+ mat4x4f_size: {
+ type: `S_mat4x4f_size`,
+ decl: `struct S_mat4x4f_size {
+ x : mat4x4f,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 64,
+ },
+ mat2x2h_size: {
+ type: `S_mat2x2h_size`,
+ decl: `struct S_mat2x2h_size {
+ x : mat2x2h,
+ y : f16,
+ }`,
+ read_assign: `out = u32(in.y)`,
+ write_assign: `out.y = f16(in)`,
+ offset: 8,
+ f16: true,
+ },
+ mat3x2h_size: {
+ type: `S_mat3x2h_size`,
+ decl: `struct S_mat3x2h_size {
+ x : mat3x2h,
+ y : f16,
+ }`,
+ read_assign: `out = u32(in.y)`,
+ write_assign: `out.y = f16(in)`,
+ offset: 12,
+ f16: true,
+ },
+ mat4x2h_size: {
+ type: `S_mat4x2h_size`,
+ decl: `struct S_mat4x2h_size {
+ x : mat4x2h,
+ y : f16,
+ }`,
+ read_assign: `out = u32(in.y)`,
+ write_assign: `out.y = f16(in)`,
+ offset: 16,
+ f16: true,
+ },
+ mat2x3h_size: {
+ type: `S_mat2x3h_size`,
+ decl: `struct S_mat2x3h_size {
+ x : mat2x3h,
+ y : f16,
+ }`,
+ read_assign: `out = u32(in.y)`,
+ write_assign: `out.y = f16(in)`,
+ offset: 16,
+ f16: true,
+ },
+ mat3x3h_size: {
+ type: `S_mat3x3h_size`,
+ decl: `struct S_mat3x3h_size {
+ x : mat3x3h,
+ y : f16,
+ }`,
+ read_assign: `out = u32(in.y)`,
+ write_assign: `out.y = f16(in)`,
+ offset: 24,
+ f16: true,
+ },
+ mat4x3h_size: {
+ type: `S_mat4x3h_size`,
+ decl: `struct S_mat4x3h_size {
+ x : mat4x3h,
+ y : f16,
+ }`,
+ read_assign: `out = u32(in.y)`,
+ write_assign: `out.y = f16(in)`,
+ offset: 32,
+ f16: true,
+ },
+ mat2x4h_size: {
+ type: `S_mat2x4h_size`,
+ decl: `struct S_mat2x4h_size {
+ x : mat2x4h,
+ y : f16,
+ }`,
+ read_assign: `out = u32(in.y)`,
+ write_assign: `out.y = f16(in)`,
+ offset: 16,
+ f16: true,
+ },
+ mat3x4h_size: {
+ type: `S_mat3x4h_size`,
+ decl: `struct S_mat3x4h_size {
+ x : mat3x4h,
+ y : f16,
+ }`,
+ read_assign: `out = u32(in.y)`,
+ write_assign: `out.y = f16(in)`,
+ offset: 24,
+ f16: true,
+ },
+ mat4x4h_size: {
+ type: `S_mat4x4h_size`,
+ decl: `struct S_mat4x4h_size {
+ x : mat4x4h,
+ y : f16,
+ }`,
+ read_assign: `out = u32(in.y)`,
+ write_assign: `out.y = f16(in)`,
+ offset: 32,
+ f16: true,
+ },
+ struct_align_vec2i: {
+ type: `S_struct_align_vec2i`,
+ decl: `struct Inner {
+ x : u32,
+ y : vec2i,
+ }
+ struct S_struct_align_vec2i {
+ x : u32,
+ y : Inner,
+ }`,
+ read_assign: `out = in.y.x`,
+ write_assign: `out.y.x = in`,
+ offset: 8,
+ skip_uniform: true,
+ },
+ struct_align_vec3i: {
+ type: `S_struct_align_vec3i`,
+ decl: `struct Inner {
+ x : u32,
+ y : vec3i,
+ }
+ struct S_struct_align_vec3i {
+ x : u32,
+ y : Inner,
+ }`,
+ read_assign: `out = in.y.x`,
+ write_assign: `out.y.x = in`,
+ offset: 16,
+ },
+ struct_align_vec4i: {
+ type: `S_struct_align_vec4i`,
+ decl: `struct Inner {
+ x : u32,
+ y : vec4i,
+ }
+ struct S_struct_align_vec4i {
+ x : u32,
+ y : Inner,
+ }`,
+ read_assign: `out = in.y.x`,
+ write_assign: `out.y.x = in`,
+ offset: 16,
+ },
+ struct_align_vec2h: {
+ type: `S_struct_align_vec2h`,
+ decl: `struct Inner {
+ x : f16,
+ y : vec2h,
+ }
+ struct S_struct_align_vec2h {
+ x : f16,
+ y : Inner,
+ }`,
+ read_assign: `out = u32(in.y.x)`,
+ write_assign: `out.y.x = f16(in)`,
+ offset: 4,
+ f16: true,
+ skip_uniform: true,
+ },
+ struct_align_vec3h: {
+ type: `S_struct_align_vec3h`,
+ decl: `struct Inner {
+ x : f16,
+ y : vec3h,
+ }
+ struct S_struct_align_vec3h {
+ x : f16,
+ y : Inner,
+ }`,
+ read_assign: `out = u32(in.y.x)`,
+ write_assign: `out.y.x = f16(in)`,
+ offset: 8,
+ f16: true,
+ skip_uniform: true,
+ },
+ struct_align_vec4h: {
+ type: `S_struct_align_vec4h`,
+ decl: `struct Inner {
+ x : f16,
+ y : vec4h,
+ }
+ struct S_struct_align_vec4h {
+ x : f16,
+ y : Inner,
+ }`,
+ read_assign: `out = u32(in.y.x)`,
+ write_assign: `out.y.x = f16(in)`,
+ offset: 8,
+ f16: true,
+ skip_uniform: true,
+ },
+ struct_size_roundup: {
+ type: `S_struct_size_roundup`,
+ decl: `struct Inner {
+ x : vec3u,
+ }
+ struct S_struct_size_roundup {
+ x : Inner,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 16,
+ },
+ struct_inner_size: {
+ type: `S_struct_inner_size`,
+ decl: `struct Inner {
+ @size(112) x : u32,
+ }
+ struct S_struct_inner_size {
+ x : Inner,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 112,
+ },
+ struct_inner_align: {
+ type: `S_struct_inner_align`,
+ decl: `struct Inner {
+ @align(64) x : u32,
+ }
+ struct S_struct_inner_align {
+ x : u32,
+ y : Inner,
+ }`,
+ read_assign: `out = in.y.x`,
+ write_assign: `out.y.x = in`,
+ offset: 64,
+ },
+ struct_inner_size_and_align: {
+ type: `S_struct_inner_size_and_align`,
+ decl: `struct Inner {
+ @align(32) @size(33) x : u32,
+ }
+ struct S_struct_inner_size_and_align {
+ x : Inner,
+ y : Inner,
+ }`,
+ read_assign: `out = in.y.x`,
+ write_assign: `out.y.x = in`,
+ offset: 64,
+ },
+ struct_override_size: {
+ type: `S_struct_override_size`,
+ decl: `struct Inner {
+ @size(32) x : u32,
+ }
+ struct S_struct_override_size {
+ @size(64) x : Inner,
+ y : u32,
+ }`,
+ read_assign: `out = in.y`,
+ write_assign: `out.y = in`,
+ offset: 64,
+ },
+ struct_double_align: {
+ type: `S_struct_double_align`,
+ decl: `struct Inner {
+ x : u32,
+ @align(32) y : u32,
+ }
+ struct S_struct_double_align {
+ x : u32,
+ @align(64) y : Inner,
+ }`,
+ read_assign: `out = in.y.y`,
+ write_assign: `out.y.y = in`,
+ offset: 96,
+ },
+ array_vec3u_align: {
+ type: `S_array_vec3u_align`,
+ decl: `struct S_array_vec3u_align {
+ x : u32,
+ y : array<vec3u, 2>,
+ }`,
+ read_assign: `out = in.y[0][0]`,
+ write_assign: `out.y[0][0] = in`,
+ offset: 16,
+ },
+ array_vec3h_align: {
+ type: `S_array_vec3h_align`,
+ decl: `struct S_array_vec3h_align {
+ x : f16,
+ y : array<vec3h, 2>,
+ }`,
+ read_assign: `out = u32(in.y[0][0])`,
+ write_assign: `out.y[0][0] = f16(in)`,
+ offset: 8,
+ f16: true,
+ skip_uniform: true,
+ },
+ array_vec3u_stride: {
+ type: `S_array_vec3u_stride`,
+ decl: `struct S_array_vec3u_stride {
+ x : array<vec3u, 4>,
+ }`,
+ read_assign: `out = in.x[1][0]`,
+ write_assign: `out.x[1][0] = in`,
+ offset: 16,
+ },
+ array_vec3h_stride: {
+ type: `S_array_vec3h_stride`,
+ decl: `struct S_array_vec3h_stride {
+ x : array<vec3h, 4>,
+ }`,
+ read_assign: `out = u32(in.x[1][0])`,
+ write_assign: `out.x[1][0] = f16(in)`,
+ offset: 8,
+ f16: true,
+ skip_uniform: true,
+ },
+ array_stride_size: {
+ type: `array<S_stride, 4>`,
+ decl: `struct S_stride {
+ @size(16) x : u32,
+ }`,
+ read_assign: `out = in[2].x`,
+ write_assign: `out[2].x = in`,
+ offset: 32,
+ },
+};
+
+g.test('read_layout')
+ .desc('Test reading memory layouts')
+ .params(u =>
+ u
+ .combine('case', keysOf(kLayoutCases))
+ .combine('aspace', ['storage', 'uniform', 'workgroup', 'function', 'private'] as const)
+ .beginSubcases()
+ )
+ .beforeAllSubcases(t => {
+ const testcase = kLayoutCases[t.params.case];
+ if (testcase.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ // Don't test atomics in workgroup due to initialization boilerplate.
+ t.skipIf(
+ testcase.type.includes('atomic') && t.params.aspace !== 'storage',
+ `Skipping atomic test for non-storage address space`
+ );
+
+ t.skipIf(
+ testcase.skip_uniform === true && t.params.aspace === 'uniform',
+ `Uniform requires 16 byte alignment`
+ );
+ })
+ .fn(t => {
+ const testcase = kLayoutCases[t.params.case];
+ let code = `
+${testcase.f16 ? 'enable f16;' : ''}
+${testcase.decl}
+
+@group(0) @binding(1)
+var<storage, read_write> out : u32;
+`;
+
+ if (t.params.aspace === 'uniform') {
+ code += `@group(0) @binding(0)
+ var<${t.params.aspace}> in : ${testcase.type};`;
+ } else if (t.params.aspace === 'storage') {
+ // Use read_write for input data to support atomics.
+ code += `@group(0) @binding(0)
+ var<${t.params.aspace}, read_write> in : ${testcase.type};`;
+ } else {
+ code += `@group(0) @binding(0)
+ var<storage> pre_in : ${testcase.type};`;
+ if (t.params.aspace === 'workgroup') {
+ code += `
+ var<workgroup> in : ${testcase.type};`;
+ } else if (t.params.aspace === 'private') {
+ code += `
+ var<private> in : ${testcase.type};`;
+ }
+ }
+
+ code += `
+@compute @workgroup_size(1,1,1)
+fn main() {
+`;
+
+ if (
+ t.params.aspace === 'workgroup' ||
+ t.params.aspace === 'function' ||
+ t.params.aspace === 'private'
+ ) {
+ if (t.params.aspace === 'function') {
+ code += `var in : ${testcase.type};\n`;
+ }
+ code += `in = pre_in;`;
+ if (t.params.aspace === 'workgroup') {
+ code += `workgroupBarrier();\n`;
+ }
+ }
+
+ code += `\n${testcase.read_assign};\n}`;
+
+ let usage = GPUBufferUsage.COPY_SRC;
+ if (t.params.aspace === 'uniform') {
+ usage |= GPUBufferUsage.UNIFORM;
+ } else {
+ usage |= GPUBufferUsage.STORAGE;
+ }
+
+ // Magic number is 42 in various representations.
+ const inMagicNumber = testcase.f16 ? 0x5140 : testcase.f32 ? 0x42280000 : 42;
+ const in_buffer = t.makeBufferWithContents(
+ new Uint32Array([
+ ...iterRange(128, x => {
+ if (x * 4 === testcase.offset) {
+ return inMagicNumber;
+ } else {
+ return 0;
+ }
+ }),
+ ]),
+ usage
+ );
+ t.trackForCleanup(in_buffer);
+
+ const out_buffer = t.makeBufferWithContents(
+ new Uint32Array([...iterRange(1, x => 0)]),
+ GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ );
+ t.trackForCleanup(out_buffer);
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code,
+ }),
+ entryPoint: 'main',
+ },
+ });
+
+ const bg = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: in_buffer,
+ },
+ },
+ {
+ binding: 1,
+ resource: {
+ buffer: out_buffer,
+ },
+ },
+ ],
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bg);
+ pass.dispatchWorkgroups(1, 1, 1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(out_buffer, new Uint32Array([42]));
+ });
+
+g.test('write_layout')
+ .desc('Test writing memory layouts')
+ .params(u =>
+ u
+ .combine('case', keysOf(kLayoutCases))
+ .combine('aspace', ['storage', 'workgroup', 'function', 'private'] as const)
+ .beginSubcases()
+ )
+ .beforeAllSubcases(t => {
+ const testcase = kLayoutCases[t.params.case];
+ if (testcase.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ // Don't test atomics in workgroup due to initialization boilerplate.
+ t.skipIf(
+ testcase.type.includes('atomic') && t.params.aspace !== 'storage',
+ `Skipping atomic test for non-storage address space`
+ );
+ })
+ .fn(t => {
+ const testcase = kLayoutCases[t.params.case];
+ let code = `
+${testcase.f16 ? 'enable f16;' : ''}
+${testcase.decl}
+
+@group(0) @binding(0)
+var<storage> in : u32;
+`;
+
+ if (t.params.aspace === 'storage') {
+ code += `@group(0) @binding(1)
+ var<storage, read_write> out : ${testcase.type};\n`;
+ } else {
+ code += `@group(0) @binding(1)
+ var<storage, read_write> post_out : ${testcase.type};\n`;
+
+ if (t.params.aspace === 'workgroup') {
+ code += `var<workgroup> out : ${testcase.type};\n`;
+ } else if (t.params.aspace === 'private') {
+ code += `var<private> out : ${testcase.type};\n`;
+ }
+ }
+
+ code += `
+@compute @workgroup_size(1,1,1)
+fn main() {
+`;
+
+ if (t.params.aspace === 'function') {
+ code += `var out : ${testcase.type};\n`;
+ }
+
+ code += `${testcase.write_assign};\n`;
+ if (
+ t.params.aspace === 'workgroup' ||
+ t.params.aspace === 'function' ||
+ t.params.aspace === 'private'
+ ) {
+ if (t.params.aspace === 'workgroup') {
+ code += `workgroupBarrier();\n`;
+ }
+ code += `post_out = out;`;
+ }
+
+ code += `\n}`;
+
+ const in_buffer = t.makeBufferWithContents(
+ new Uint32Array([42]),
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ );
+ t.trackForCleanup(in_buffer);
+
+ const out_buffer = t.makeBufferWithContents(
+ new Uint32Array([...iterRange(128, x => 0)]),
+ GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ );
+ t.trackForCleanup(out_buffer);
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code,
+ }),
+ entryPoint: 'main',
+ },
+ });
+
+ const bg = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: in_buffer,
+ },
+ },
+ {
+ binding: 1,
+ resource: {
+ buffer: out_buffer,
+ },
+ },
+ ],
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bg);
+ pass.dispatchWorkgroups(1, 1, 1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ // Magic number is 42 in various representations.
+ const outMagicNumber = testcase.f16 ? 0x5140 : testcase.f32 ? 0x42280000 : 42;
+ const expect = new Uint32Array([
+ ...iterRange(128, x => {
+ if (x * 4 === testcase.offset) {
+ return outMagicNumber;
+ } else {
+ return 0;
+ }
+ }),
+ ]);
+
+ t.expectGPUBufferValuesEqual(out_buffer, expect);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/barrier.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/barrier.spec.ts
index 478ae28a7a..72fb69e293 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/barrier.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/barrier.spec.ts
@@ -43,22 +43,33 @@ const memoryModelTestParams: MemoryModelTestParams = {
numBehaviors: 2,
};
-// The two kinds of non-atomic accesses tested.
+// The three kinds of non-atomic accesses tested.
// rw: read -> barrier -> write
// wr: write -> barrier -> read
// ww: write -> barrier -> write
type AccessPair = 'rw' | 'wr' | 'ww';
// Test the non-atomic memory types.
-const kMemTypes = [MemoryType.NonAtomicStorageClass, MemoryType.NonAtomicWorkgroupClass] as const;
+const kMemTypes = [
+ MemoryType.NonAtomicStorageClass,
+ MemoryType.NonAtomicWorkgroupClass,
+ MemoryType.NonAtomicTextureClass,
+] as const;
const storageMemoryBarrierStoreLoadTestCode = `
test_locations.value[x_0] = 1;
- workgroupBarrier();
+ storageBarrier();
let r0 = u32(test_locations.value[x_1]);
atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
`;
+const textureMemoryBarrierStoreLoadTestCode = `
+ textureStore(texture_locations, indexToCoord(x_0), vec4u(1));
+ textureBarrier();
+ let r0 = textureLoad(texture_locations, indexToCoord(x_1)).x;
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
+`;
+
const workgroupMemoryBarrierStoreLoadTestCode = `
wg_test_locations[x_0] = 1;
workgroupBarrier();
@@ -66,13 +77,27 @@ const workgroupMemoryBarrierStoreLoadTestCode = `
atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
`;
+const workgroupUniformLoadMemoryBarrierStoreLoadTestCode = `
+ wg_test_locations[x_0] = 1;
+ _ = workgroupUniformLoad(&placeholder_wg_var);
+ let r0 = u32(wg_test_locations[x_1]);
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
+`;
+
const storageMemoryBarrierLoadStoreTestCode = `
let r0 = u32(test_locations.value[x_0]);
- workgroupBarrier();
+ storageBarrier();
test_locations.value[x_1] = 1;
atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
`;
+const textureMemoryBarrierLoadStoreTestCode = `
+ let r0 = textureLoad(texture_locations, indexToCoord(x_0)).x;
+ textureBarrier();
+ textureStore(texture_locations, indexToCoord(x_1), vec4u(1));
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+`;
+
const workgroupMemoryBarrierLoadStoreTestCode = `
let r0 = u32(wg_test_locations[x_0]);
workgroupBarrier();
@@ -80,12 +105,27 @@ const workgroupMemoryBarrierLoadStoreTestCode = `
atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
`;
+const workgroupUniformLoadMemoryBarrierLoadStoreTestCode = `
+ let r0 = u32(wg_test_locations[x_0]);
+ _ = workgroupUniformLoad(&placeholder_wg_var);
+ wg_test_locations[x_1] = 1;
+ atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
+`;
+
const storageMemoryBarrierStoreStoreTestCode = `
test_locations.value[x_0] = 1;
storageBarrier();
test_locations.value[x_1] = 2;
`;
+const textureMemoryBarrierStoreStoreTestCode = `
+ textureStore(texture_locations, indexToCoord(x_0), vec4u(1));
+ textureBarrier();
+ textureStore(texture_locations, indexToCoord(x_1), vec4u(2));
+ textureBarrier();
+ test_locations.value[x_1] = textureLoad(texture_locations, indexToCoord(x_1)).x;
+`;
+
const workgroupMemoryBarrierStoreStoreTestCode = `
wg_test_locations[x_0] = 1;
workgroupBarrier();
@@ -94,20 +134,56 @@ const workgroupMemoryBarrierStoreStoreTestCode = `
test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + x_1] = wg_test_locations[x_1];
`;
-function getTestCode(p: { memType: MemoryType; accessPair: AccessPair }): string {
+const workgroupUniformLoadMemoryBarrierStoreStoreTestCode = `
+ wg_test_locations[x_0] = 1;
+ _ = workgroupUniformLoad(&placeholder_wg_var);
+ wg_test_locations[x_1] = 2;
+ _ = workgroupUniformLoad(&placeholder_wg_var);
+ test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + x_1] = wg_test_locations[x_1];
+`;
+
+function getTestCode(p: {
+ memType: MemoryType;
+ accessPair: AccessPair;
+ normalBarrier: boolean;
+}): string {
switch (p.accessPair) {
- case 'rw':
- return p.memType === MemoryType.NonAtomicStorageClass
- ? storageMemoryBarrierLoadStoreTestCode
- : workgroupMemoryBarrierLoadStoreTestCode;
- case 'wr':
- return p.memType === MemoryType.NonAtomicStorageClass
- ? storageMemoryBarrierStoreLoadTestCode
- : workgroupMemoryBarrierStoreLoadTestCode;
- case 'ww':
- return p.memType === MemoryType.NonAtomicStorageClass
- ? storageMemoryBarrierStoreStoreTestCode
- : workgroupMemoryBarrierStoreStoreTestCode;
+ case 'rw': {
+ switch (p.memType) {
+ case MemoryType.NonAtomicStorageClass:
+ return storageMemoryBarrierLoadStoreTestCode;
+ case MemoryType.NonAtomicTextureClass:
+ return textureMemoryBarrierLoadStoreTestCode;
+ default:
+ return p.normalBarrier
+ ? workgroupMemoryBarrierLoadStoreTestCode
+ : workgroupUniformLoadMemoryBarrierLoadStoreTestCode;
+ }
+ }
+ case 'wr': {
+ switch (p.memType) {
+ case MemoryType.NonAtomicStorageClass:
+ return storageMemoryBarrierStoreLoadTestCode;
+ case MemoryType.NonAtomicTextureClass:
+ return textureMemoryBarrierStoreLoadTestCode;
+ default:
+ return p.normalBarrier
+ ? workgroupMemoryBarrierStoreLoadTestCode
+ : workgroupUniformLoadMemoryBarrierStoreLoadTestCode;
+ }
+ }
+ case 'ww': {
+ switch (p.memType) {
+ case MemoryType.NonAtomicStorageClass:
+ return storageMemoryBarrierStoreStoreTestCode;
+ case MemoryType.NonAtomicTextureClass:
+ return textureMemoryBarrierStoreStoreTestCode;
+ default:
+ return p.normalBarrier
+ ? workgroupMemoryBarrierStoreStoreTestCode
+ : workgroupUniformLoadMemoryBarrierStoreStoreTestCode;
+ }
+ }
}
}
@@ -123,13 +199,28 @@ g.test('workgroup_barrier_store_load')
.combine('accessValueType', kAccessValueTypes)
.combine('memType', kMemTypes)
.combine('accessPair', ['wr'] as const)
+ .combine('normalBarrier', [true, false] as const)
)
.beforeAllSubcases(t => {
if (t.params.accessValueType === 'f16') {
t.selectDeviceOrSkipTestCase('shader-f16');
}
+ t.skipIf(
+ !t.params.normalBarrier && t.params.memType !== MemoryType.NonAtomicWorkgroupClass,
+ 'workgroupUniformLoad does not have storage memory semantics'
+ );
+ t.skipIf(
+ t.params.memType === MemoryType.NonAtomicTextureClass && t.params.accessValueType === 'f16',
+ 'textures do not support f16 access'
+ );
})
.fn(async t => {
+ t.skipIf(
+ t.params.memType === MemoryType.NonAtomicTextureClass &&
+ !t.hasLanguageFeature('readonly_and_readwrite_storage_textures'),
+ 'requires RW storage textures feature'
+ );
+
const resultCode = `
if (r0 == 1u) {
atomicAdd(&test_results.seq, 1u);
@@ -137,11 +228,14 @@ g.test('workgroup_barrier_store_load')
atomicAdd(&test_results.weak, 1u);
}
`;
- const testShader = buildTestShader(
+ let testShader = buildTestShader(
getTestCode(t.params),
t.params.memType,
TestType.IntraWorkgroup
);
+ if (!t.params.normalBarrier) {
+ testShader += '\nvar<workgroup> placeholder_wg_var : u32;\n';
+ }
const resultShader = buildResultShader(
resultCode,
TestType.IntraWorkgroup,
@@ -152,7 +246,8 @@ g.test('workgroup_barrier_store_load')
memoryModelTestParams,
testShader,
resultShader,
- t.params.accessValueType
+ t.params.accessValueType,
+ t.params.memType === MemoryType.NonAtomicTextureClass
);
await memModelTester.run(15, 1);
});
@@ -169,13 +264,28 @@ g.test('workgroup_barrier_load_store')
.combine('accessValueType', kAccessValueTypes)
.combine('memType', kMemTypes)
.combine('accessPair', ['rw'] as const)
+ .combine('normalBarrier', [true, false] as const)
)
.beforeAllSubcases(t => {
if (t.params.accessValueType === 'f16') {
t.selectDeviceOrSkipTestCase('shader-f16');
}
+ t.skipIf(
+ !t.params.normalBarrier && t.params.memType !== MemoryType.NonAtomicWorkgroupClass,
+ 'workgroupUniformLoad does not have storage memory semantics'
+ );
+ t.skipIf(
+ t.params.memType === MemoryType.NonAtomicTextureClass && t.params.accessValueType === 'f16',
+ 'textures do not support f16 access'
+ );
})
.fn(async t => {
+ t.skipIf(
+ t.params.memType === MemoryType.NonAtomicTextureClass &&
+ !t.hasLanguageFeature('readonly_and_readwrite_storage_textures'),
+ 'requires RW storage textures feature'
+ );
+
const resultCode = `
if (r0 == 0u) {
atomicAdd(&test_results.seq, 1u);
@@ -183,11 +293,14 @@ g.test('workgroup_barrier_load_store')
atomicAdd(&test_results.weak, 1u);
}
`;
- const testShader = buildTestShader(
+ let testShader = buildTestShader(
getTestCode(t.params),
t.params.memType,
TestType.IntraWorkgroup
);
+ if (!t.params.normalBarrier) {
+ testShader += '\nvar<workgroup> placeholder_wg_var : u32;\n';
+ }
const resultShader = buildResultShader(
resultCode,
TestType.IntraWorkgroup,
@@ -198,7 +311,8 @@ g.test('workgroup_barrier_load_store')
memoryModelTestParams,
testShader,
resultShader,
- t.params.accessValueType
+ t.params.accessValueType,
+ t.params.memType === MemoryType.NonAtomicTextureClass
);
await memModelTester.run(12, 1);
});
@@ -215,13 +329,28 @@ g.test('workgroup_barrier_store_store')
.combine('accessValueType', kAccessValueTypes)
.combine('memType', kMemTypes)
.combine('accessPair', ['ww'] as const)
+ .combine('normalBarrier', [true, false] as const)
)
.beforeAllSubcases(t => {
if (t.params.accessValueType === 'f16') {
t.selectDeviceOrSkipTestCase('shader-f16');
}
+ t.skipIf(
+ !t.params.normalBarrier && t.params.memType !== MemoryType.NonAtomicWorkgroupClass,
+ 'workgroupUniformLoad does not have storage memory semantics'
+ );
+ t.skipIf(
+ t.params.memType === MemoryType.NonAtomicTextureClass && t.params.accessValueType === 'f16',
+ 'textures do not support f16 access'
+ );
})
.fn(async t => {
+ t.skipIf(
+ t.params.memType === MemoryType.NonAtomicTextureClass &&
+ !t.hasLanguageFeature('readonly_and_readwrite_storage_textures'),
+ 'requires RW storage textures feature'
+ );
+
const resultCode = `
if (mem_x_0 == 2u) {
atomicAdd(&test_results.seq, 1u);
@@ -229,11 +358,14 @@ g.test('workgroup_barrier_store_store')
atomicAdd(&test_results.weak, 1u);
}
`;
- const testShader = buildTestShader(
+ let testShader = buildTestShader(
getTestCode(t.params),
t.params.memType,
TestType.IntraWorkgroup
);
+ if (!t.params.normalBarrier) {
+ testShader += '\nvar<workgroup> placeholder_wg_var : u32;\n';
+ }
const resultShader = buildResultShader(
resultCode,
TestType.IntraWorkgroup,
@@ -244,7 +376,8 @@ g.test('workgroup_barrier_store_store')
memoryModelTestParams,
testShader,
resultShader,
- t.params.accessValueType
+ t.params.accessValueType,
+ t.params.memType === MemoryType.NonAtomicTextureClass
);
await memModelTester.run(10, 1);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/memory_model_setup.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/memory_model_setup.ts
index f8e5b9034c..8dee32b72d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/memory_model_setup.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/memory_model_setup.ts
@@ -1,5 +1,7 @@
-import { GPUTest } from '../../../gpu_test';
+import { GPUTest } from '../../../gpu_test.js';
import { checkElementsPassPredicate } from '../../../util/check_contents.js';
+import { align } from '../../../util/math.js';
+import { PRNG } from '../../../util/prng.js';
/* All buffer sizes are counted in units of 4-byte words. */
@@ -15,6 +17,9 @@ import { checkElementsPassPredicate } from '../../../util/check_contents.js';
export type AccessValueType = 'f16' | 'u32';
export const kAccessValueTypes = ['f16', 'u32'] as const;
+/** The width used for textures (default compat limit in WebGPU). */
+const kWidth = 4096;
+
/* Parameter values are set heuristically, typically by a time-intensive search. */
export type MemoryModelTestParams = {
/* Number of invocations per workgroup. The workgroups are 1-dimensional. */
@@ -76,12 +81,33 @@ const numReadOutputs = 2;
type BufferWithSource = {
/** Buffer used by shader code. */
deviceBuf: GPUBuffer;
- /** Buffer populated from the host size, data is copied to device buffer for use by shader. */
+ /** Buffer populated from the host side, data is copied to device buffer for use by shader. */
srcBuf: GPUBuffer;
/** Size in bytes of the buffer. */
size: number;
};
+/** Represents a device texture and a utility buffer for resetting memory and copying parameters. */
+type TextureWithSource = {
+ /** Texture used by shader code. */
+ deviceTex: GPUTexture;
+ /** Buffer populated from the host side, data is copied to device buffer for use by shader. */
+ srcBuf: GPUBuffer;
+ /** Size in bytes of the buffer. */
+ size: number;
+};
+
+type SubBufferWithSource = {
+ /** Buffer used by shader code. This buffer is shared for multiple used */
+ deviceBuf: GPUBuffer;
+ /** Buffer populated from the host side, data is copied to device buffer for use by shader. */
+ srcBuf: GPUBuffer;
+ /** Size in bytes of this portion of the buffer. */
+ size: number;
+ /** Offset in bytes of this portion of the buffer */
+ offset: number;
+};
+
/** Specifies the buffers used during a memory model test. */
type MemoryModelBuffers = {
/** This is the memory region that testing threads read from and write to. */
@@ -102,6 +128,10 @@ type MemoryModelBuffers = {
stressParams: BufferWithSource;
};
+type MemoryModelTextures = {
+ testLocations: TextureWithSource;
+};
+
/** The number of stress params to add to the stress params buffer. */
const numStressParams = 12;
const barrierParamIndex = 0;
@@ -128,11 +158,11 @@ const bytesPerWord = 4;
* - enable directives, if necessary
* - the type alias for AccessValueType
*/
-function shaderPreamble(accessValueType: AccessValueType): string {
+function shaderPreamble(accessValueType: AccessValueType, constants: string): string {
if (accessValueType === 'f16') {
- return 'enable f16;\nalias AccessValueTy = f16;\n';
+ return `enable f16;\nalias AccessValueTy = f16;\n${constants}\n`;
}
- return `alias AccessValueTy = ${accessValueType};\n`;
+ return `alias AccessValueTy = ${accessValueType};\n${constants}\n`;
}
/**
@@ -175,10 +205,14 @@ export class MemoryModelTester {
protected test: GPUTest;
protected params: MemoryModelTestParams;
protected buffers: MemoryModelBuffers;
+ protected textures: MemoryModelTextures | undefined;
protected testPipeline: GPUComputePipeline;
protected testBindGroup: GPUBindGroup;
+ protected textureBindGroup: GPUBindGroup | undefined;
protected resultPipeline: GPUComputePipeline;
protected resultBindGroup: GPUBindGroup;
+ protected prng: PRNG;
+ protected useTexture: boolean;
/** Sets up a memory model test by initializing buffers and pipeline layouts. */
constructor(
@@ -186,24 +220,36 @@ export class MemoryModelTester {
params: MemoryModelTestParams,
testShader: string,
resultShader: string,
- accessValueType: AccessValueType = 'u32'
+ accessValueType: AccessValueType = 'u32',
+ useTexture: boolean = false
) {
+ this.prng = new PRNG(1);
this.test = t;
this.params = params;
-
- testShader = shaderPreamble(accessValueType) + testShader;
- resultShader = shaderPreamble(accessValueType) + resultShader;
+ this.useTexture = useTexture;
+
+ const workgroupXSize = Math.min(params.workgroupSize, t.device.limits.maxComputeWorkgroupSizeX);
+ const constants = `
+ const kNumBarriers = 1u; // MAINTENANCE_TODO: make barrier not an array
+ const kMaxWorkgroups = ${params.maxWorkgroups}u;
+ const kScratchMemorySize = ${params.scratchMemorySize}u;
+ const kWorkgroupXSize = ${workgroupXSize}u;
+ `;
+ testShader = shaderPreamble(accessValueType, constants) + testShader;
+ resultShader = shaderPreamble(accessValueType, constants) + resultShader;
// set up buffers
- const testingThreads = this.params.workgroupSize * this.params.testingWorkgroups;
+ const testingThreads = workgroupXSize * this.params.testingWorkgroups;
const testLocationsSize =
testingThreads * numMemLocations * this.params.memStride * bytesPerWord;
const testLocationsBuffer: BufferWithSource = {
deviceBuf: this.test.device.createBuffer({
+ label: 'testLocationsBuffer',
size: testLocationsSize,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
}),
srcBuf: this.test.device.createBuffer({
+ label: 'testLocationsSrcBuf',
size: testLocationsSize,
usage: GPUBufferUsage.COPY_SRC,
}),
@@ -213,10 +259,12 @@ export class MemoryModelTester {
const readResultsSize = testingThreads * numReadOutputs * bytesPerWord;
const readResultsBuffer: BufferWithSource = {
deviceBuf: this.test.device.createBuffer({
+ label: 'readResultsBuffer',
size: readResultsSize,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
}),
srcBuf: this.test.device.createBuffer({
+ label: 'readResultsSrcBuf',
size: readResultsSize,
usage: GPUBufferUsage.COPY_SRC,
}),
@@ -226,10 +274,12 @@ export class MemoryModelTester {
const testResultsSize = this.params.numBehaviors * bytesPerWord;
const testResultsBuffer: BufferWithSource = {
deviceBuf: this.test.device.createBuffer({
+ label: 'testResultsBuffer',
size: testResultsSize,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
}),
srcBuf: this.test.device.createBuffer({
+ label: 'testResultsSrcBuffer',
size: testResultsSize,
usage: GPUBufferUsage.COPY_SRC,
}),
@@ -249,52 +299,87 @@ export class MemoryModelTester {
size: shuffledWorkgroupsSize,
};
- const barrierSize = bytesPerWord;
- const barrierBuffer: BufferWithSource = {
- deviceBuf: this.test.device.createBuffer({
- size: barrierSize,
- usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
- }),
+ if (this.useTexture) {
+ const numTexels = testLocationsSize / bytesPerWord;
+ const width = kWidth;
+ const height = numTexels / width;
+ const textureSize: GPUExtent3D = { width, height };
+ const textureLocations: TextureWithSource = {
+ deviceTex: this.test.device.createTexture({
+ format: 'r32uint',
+ dimension: '2d',
+ size: textureSize,
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.STORAGE_BINDING,
+ }),
+ srcBuf: testLocationsBuffer.srcBuf,
+ size: testLocationsSize,
+ };
+ this.textures = {
+ testLocations: textureLocations,
+ };
+ }
+
+ // Combine 3 arrays into 1 buffer as we need to keep the number of storage buffers to 4 for compat.
+ const falseSharingAvoidanceQuantum = 4096;
+ const barrierSize = align(bytesPerWord, falseSharingAvoidanceQuantum);
+ const scratchpadSize = align(
+ this.params.scratchMemorySize * bytesPerWord,
+ falseSharingAvoidanceQuantum
+ );
+ const scratchMemoryLocationsSize = align(
+ this.params.maxWorkgroups * bytesPerWord,
+ falseSharingAvoidanceQuantum
+ );
+ const comboSize = barrierSize + scratchpadSize + scratchMemoryLocationsSize;
+
+ const comboBuffer = this.test.device.createBuffer({
+ label: 'comboBuffer',
+ size: comboSize,
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
+ });
+
+ const barrierBuffer: SubBufferWithSource = {
+ deviceBuf: comboBuffer,
srcBuf: this.test.device.createBuffer({
+ label: 'barrierSrcBuf',
size: barrierSize,
usage: GPUBufferUsage.COPY_SRC,
}),
size: barrierSize,
+ offset: 0,
};
- const scratchpadSize = this.params.scratchMemorySize * bytesPerWord;
- const scratchpadBuffer: BufferWithSource = {
- deviceBuf: this.test.device.createBuffer({
- size: scratchpadSize,
- usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
- }),
+ const scratchpadBuffer: SubBufferWithSource = {
+ deviceBuf: comboBuffer,
srcBuf: this.test.device.createBuffer({
+ label: 'scratchpadSrcBuf',
size: scratchpadSize,
usage: GPUBufferUsage.COPY_SRC,
}),
size: scratchpadSize,
+ offset: barrierSize,
};
- const scratchMemoryLocationsSize = this.params.maxWorkgroups * bytesPerWord;
- const scratchMemoryLocationsBuffer: BufferWithSource = {
- deviceBuf: this.test.device.createBuffer({
- size: scratchMemoryLocationsSize,
- usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
- }),
+ const scratchMemoryLocationsBuffer: SubBufferWithSource = {
+ deviceBuf: comboBuffer,
srcBuf: this.test.device.createBuffer({
+ label: 'scratchMemoryLocationsSrcBuf',
size: scratchMemoryLocationsSize,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE,
}),
size: scratchMemoryLocationsSize,
+ offset: barrierSize + scratchpadSize,
};
const stressParamsSize = numStressParams * bytesPerWord;
const stressParamsBuffer: BufferWithSource = {
deviceBuf: this.test.device.createBuffer({
+ label: 'stressParamsBuffer',
size: stressParamsSize,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM,
}),
srcBuf: this.test.device.createBuffer({
+ label: 'stressParamsSrcBuf',
size: stressParamsSize,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE,
}),
@@ -314,19 +399,50 @@ export class MemoryModelTester {
// set up pipeline layouts
const testLayout = this.test.device.createBindGroupLayout({
+ label: 'testLayout',
entries: [
{ binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
{ binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
{ binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } },
{ binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
- { binding: 4, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
- { binding: 5, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
- { binding: 6, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } },
+ { binding: 4, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } },
],
});
+
+ let layouts: GPUBindGroupLayout[] = [testLayout];
+ if (this.useTexture) {
+ const textureLayout = this.test.device.createBindGroupLayout({
+ label: 'textureLayout',
+ entries: [
+ {
+ binding: 0,
+ visibility: GPUShaderStage.COMPUTE,
+ storageTexture: {
+ access: 'read-write',
+ format: 'r32uint',
+ viewDimension: '2d',
+ },
+ },
+ ],
+ });
+ layouts = [testLayout, textureLayout];
+
+ const texLocations = (this.textures as MemoryModelTextures).testLocations.deviceTex;
+ this.textureBindGroup = this.test.device.createBindGroup({
+ label: 'textureBindGroup',
+ entries: [
+ {
+ binding: 0,
+ resource: texLocations.createView(),
+ },
+ ],
+ layout: textureLayout,
+ });
+ }
this.testPipeline = this.test.device.createComputePipeline({
+ label: 'testPipeline',
layout: this.test.device.createPipelineLayout({
- bindGroupLayouts: [testLayout],
+ bindGroupLayouts: layouts,
}),
compute: {
module: this.test.device.createShaderModule({
@@ -336,19 +452,19 @@ export class MemoryModelTester {
},
});
this.testBindGroup = this.test.device.createBindGroup({
+ label: 'testBindGroup',
entries: [
{ binding: 0, resource: { buffer: this.buffers.testLocations.deviceBuf } },
{ binding: 1, resource: { buffer: this.buffers.readResults.deviceBuf } },
{ binding: 2, resource: { buffer: this.buffers.shuffledWorkgroups.deviceBuf } },
- { binding: 3, resource: { buffer: this.buffers.barrier.deviceBuf } },
- { binding: 4, resource: { buffer: this.buffers.scratchpad.deviceBuf } },
- { binding: 5, resource: { buffer: this.buffers.scratchMemoryLocations.deviceBuf } },
- { binding: 6, resource: { buffer: this.buffers.stressParams.deviceBuf } },
+ { binding: 3, resource: { buffer: comboBuffer } },
+ { binding: 4, resource: { buffer: this.buffers.stressParams.deviceBuf } },
],
layout: testLayout,
});
const resultLayout = this.test.device.createBindGroupLayout({
+ label: 'resultLayout',
entries: [
{ binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
{ binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
@@ -357,6 +473,7 @@ export class MemoryModelTester {
],
});
this.resultPipeline = this.test.device.createComputePipeline({
+ label: 'resultPipeline',
layout: this.test.device.createPipelineLayout({
bindGroupLayouts: [resultLayout],
}),
@@ -368,6 +485,7 @@ export class MemoryModelTester {
},
});
this.resultBindGroup = this.test.device.createBindGroup({
+ label: 'resultBindGroup',
entries: [
{ binding: 0, resource: { buffer: this.buffers.testLocations.deviceBuf } },
{ binding: 1, resource: { buffer: this.buffers.readResults.deviceBuf } },
@@ -402,10 +520,16 @@ export class MemoryModelTester {
this.copyBufferToBuffer(encoder, this.buffers.scratchpad);
this.copyBufferToBuffer(encoder, this.buffers.scratchMemoryLocations);
this.copyBufferToBuffer(encoder, this.buffers.stressParams);
+ if (this.useTexture) {
+ this.copyBufferToTexture(encoder, (this.textures as MemoryModelTextures).testLocations);
+ }
const testPass = encoder.beginComputePass();
testPass.setPipeline(this.testPipeline);
testPass.setBindGroup(0, this.testBindGroup);
+ if (this.useTexture) {
+ testPass.setBindGroup(1, this.textureBindGroup as GPUBindGroup);
+ }
testPass.dispatchWorkgroups(numWorkgroups);
testPass.end();
@@ -443,8 +567,8 @@ export class MemoryModelTester {
* If the weak index's value is not 0, it means the test has observed a behavior disallowed by the memory model and
* is considered a test failure.
*/
- protected checkResult(weakIndex: number): (i: number, v: number) => boolean {
- return function (i: number, v: number): boolean {
+ protected checkResult(weakIndex: number): (i: number, v: number | bigint) => boolean {
+ return function (i: number, v: number | bigint): boolean {
if (i === weakIndex && v > 0) {
return false;
}
@@ -453,7 +577,7 @@ export class MemoryModelTester {
}
/** Returns a printer function that visualizes the results of checking the test results. */
- protected resultPrinter(weakIndex: number): (i: number) => string | number {
+ protected resultPrinter(weakIndex: number): (i: number) => string | number | bigint {
return function (i: number): string | number {
if (i === weakIndex) {
return 0;
@@ -464,16 +588,42 @@ export class MemoryModelTester {
}
/** Utility method that simplifies copying source buffers to device buffers. */
- protected copyBufferToBuffer(encoder: GPUCommandEncoder, buffer: BufferWithSource): void {
- encoder.copyBufferToBuffer(buffer.srcBuf, 0, buffer.deviceBuf, 0, buffer.size);
+ protected copyBufferToBuffer(
+ encoder: GPUCommandEncoder,
+ buffer: BufferWithSource | SubBufferWithSource
+ ): void {
+ encoder.copyBufferToBuffer(
+ buffer.srcBuf,
+ 0,
+ buffer.deviceBuf,
+ (buffer as SubBufferWithSource).offset || 0,
+ buffer.size
+ );
}
- /** Returns a random integer between 0 and the max. */
+ /** Utility method that simplifies copying source buffers to device textures. */
+ protected copyBufferToTexture(encoder: GPUCommandEncoder, texture: TextureWithSource): void {
+ const bytesPerWord = 4; // always uses r32uint format.
+ const numTexels = texture.size / bytesPerWord;
+ const size: GPUExtent3D = { width: kWidth, height: numTexels / kWidth };
+ encoder.copyBufferToTexture(
+ {
+ buffer: texture.srcBuf,
+ offset: 0,
+ bytesPerRow: kWidth * bytesPerWord,
+ rowsPerImage: size.height,
+ },
+ { texture: texture.deviceTex },
+ size
+ );
+ }
+
+ /** Returns a random integer in the range [0, max). */
protected getRandomInt(max: number): number {
- return Math.floor(Math.random() * max);
+ return this.prng.randomU32() % max;
}
- /** Returns a random number in between the min and max values. */
+ /** Returns a random number in the range [min, max). */
protected getRandomInRange(min: number, max: number): number {
if (min === max) {
return min;
@@ -626,7 +776,19 @@ const shaderMemStructures = `
};
struct IndexMemory {
- value: array<u32>
+ value: array<u32>,
+ };
+
+ struct AtomicMemoryBarrier {
+ value: array<atomic<u32>, kNumBarriers>
+ };
+
+ struct IndexMemoryScratchpad {
+ value: array<u32, kMaxWorkgroups>,
+ };
+
+ struct IndexMemoryScratchLocations {
+ value: array<u32, kScratchMemorySize>,
};
struct ReadResult {
@@ -635,7 +797,14 @@ const shaderMemStructures = `
};
struct ReadResults {
- value: array<ReadResult>
+ value: array<ReadResult>,
+ };
+
+ // These arrays are combine into 1 buffer because compat mode only supports 4 storage buffers by default.
+ struct CombinedData {
+ barrier: AtomicMemoryBarrier,
+ scratchpad: IndexMemoryScratchpad,
+ scratch_locations: IndexMemoryScratchLocations,
};
struct StressParamsMemory {
@@ -687,10 +856,8 @@ const twoBehaviorTestResultStructure = `
const commonTestShaderBindings = `
@group(0) @binding(1) var<storage, read_write> results : ReadResults;
@group(0) @binding(2) var<storage, read> shuffled_workgroups : IndexMemory;
- @group(0) @binding(3) var<storage, read_write> barrier : AtomicMemory;
- @group(0) @binding(4) var<storage, read_write> scratchpad : IndexMemory;
- @group(0) @binding(5) var<storage, read_write> scratch_locations : IndexMemory;
- @group(0) @binding(6) var<uniform> stress_params : StressParamsMemory;
+ @group(0) @binding(3) var<storage, read_write> combo : CombinedData;
+ @group(0) @binding(4) var<uniform> stress_params : StressParamsMemory;
`;
/** The combined bindings for a test on atomic memory. */
@@ -709,6 +876,11 @@ const nonAtomicTestShaderBindings = [
commonTestShaderBindings,
].join('\n');
+/** The extra binding for texture non-atomic texture tests. */
+const textureBindings = `
+@group(1) @binding(0) var texture_locations : texture_storage_2d<r32uint, read_write>;
+`;
+
/** Bindings used in the result aggregation phase of the test. */
const resultShaderBindings = `
@group(0) @binding(0) var<storage, read_write> test_locations : Memory;
@@ -750,6 +922,16 @@ const memoryLocationFunctions = `
}
`;
+/**
+ * Function to convert an index into an equivalent 2D coordinate for the texture.
+ */
+const textureFunctions = `
+ const kWidth = ${kWidth};
+ fn indexToCoord(idx : u32) -> vec2u {
+ return vec2u(idx % kWidth, idx / kWidth);
+ }
+`;
+
/** Functions that help add stress to the test. */
const testShaderFunctions = `
//Force the invocations in the workgroup to wait for each other, but without the general memory ordering
@@ -758,12 +940,12 @@ const testShaderFunctions = `
// the barrier but does not overly reduce testing throughput.
fn spin(limit: u32) {
var i : u32 = 0u;
- var bar_val : u32 = atomicAdd(&barrier.value[0], 1u);
+ var bar_val : u32 = atomicAdd(&combo.barrier.value[0], 1u);
loop {
if (i == 1024u || bar_val >= limit) {
break;
}
- bar_val = atomicAdd(&barrier.value[0], 0u);
+ bar_val = atomicAdd(&combo.barrier.value[0], 0u);
i = i + 1u;
}
}
@@ -773,44 +955,44 @@ const testShaderFunctions = `
// the compiler optimizing out unused loads, where 100,000 is larger than the maximum number of stress iterations used
// in any test.
fn do_stress(iterations: u32, pattern: u32, workgroup_id: u32) {
- let addr = scratch_locations.value[workgroup_id];
+ let addr = combo.scratch_locations.value[workgroup_id];
switch(pattern) {
case 0u: {
for(var i: u32 = 0u; i < iterations; i = i + 1u) {
- scratchpad.value[addr] = i;
- scratchpad.value[addr] = i + 1u;
+ combo.scratchpad.value[addr] = i;
+ combo.scratchpad.value[addr] = i + 1u;
}
}
case 1u: {
for(var i: u32 = 0u; i < iterations; i = i + 1u) {
- scratchpad.value[addr] = i;
- let tmp1: u32 = scratchpad.value[addr];
+ combo.scratchpad.value[addr] = i;
+ let tmp1: u32 = combo.scratchpad.value[addr];
if (tmp1 > 100000u) {
- scratchpad.value[addr] = i;
+ combo.scratchpad.value[addr] = i;
break;
}
}
}
case 2u: {
for(var i: u32 = 0u; i < iterations; i = i + 1u) {
- let tmp1: u32 = scratchpad.value[addr];
+ let tmp1: u32 = combo.scratchpad.value[addr];
if (tmp1 > 100000u) {
- scratchpad.value[addr] = i;
+ combo.scratchpad.value[addr] = i;
break;
}
- scratchpad.value[addr] = i;
+ combo.scratchpad.value[addr] = i;
}
}
case 3u: {
for(var i: u32 = 0u; i < iterations; i = i + 1u) {
- let tmp1: u32 = scratchpad.value[addr];
+ let tmp1: u32 = combo.scratchpad.value[addr];
if (tmp1 > 100000u) {
- scratchpad.value[addr] = i;
+ combo.scratchpad.value[addr] = i;
break;
}
- let tmp2: u32 = scratchpad.value[addr];
+ let tmp2: u32 = combo.scratchpad.value[addr];
if (tmp2 > 100000u) {
- scratchpad.value[addr] = i;
+ combo.scratchpad.value[addr] = i;
break;
}
}
@@ -827,7 +1009,7 @@ const testShaderFunctions = `
*/
const shaderEntryPoint = `
// Change to pipeline overridable constant when possible.
- const workgroupXSize = 256u;
+ const workgroupXSize = kWorkgroupXSize;
@compute @workgroup_size(workgroupXSize) fn main(
@builtin(local_invocation_id) local_invocation_id : vec3<u32>,
@builtin(workgroup_id) workgroup_id : vec3<u32>) {
@@ -980,6 +1162,18 @@ const storageMemoryNonAtomicTestShaderCode = [
testShaderCommonHeader,
].join('\n');
+/** The common shader code for the test shaders that perform non-atomic texture memory litmus tests. */
+const textureMemoryNonAtomicTestShaderCode = [
+ shaderMemStructures,
+ nonAtomicTestShaderBindings,
+ textureBindings,
+ memoryLocationFunctions,
+ textureFunctions,
+ testShaderFunctions,
+ shaderEntryPoint,
+ testShaderCommonHeader,
+].join('\n');
+
/** The common shader code for test shaders that perform atomic workgroup class memory litmus tests. */
const workgroupMemoryAtomicTestShaderCode = [
shaderMemStructures,
@@ -1023,6 +1217,8 @@ export enum MemoryType {
AtomicWorkgroupClass = 'atomic_workgroup',
/** Non-atomic memory in the workgroup address space. */
NonAtomicWorkgroupClass = 'non_atomic_workgroup',
+ /** Non-atomic memory in a texture. */
+ NonAtomicTextureClass = 'non_atomic_texture',
}
/**
@@ -1052,21 +1248,26 @@ export function buildTestShader(
testType: TestType
): string {
let memoryTypeCode;
- let isStorageAS = false;
+ let isGlobalSpace = false;
switch (memoryType) {
case MemoryType.AtomicStorageClass:
memoryTypeCode = storageMemoryAtomicTestShaderCode;
- isStorageAS = true;
+ isGlobalSpace = true;
break;
case MemoryType.NonAtomicStorageClass:
memoryTypeCode = storageMemoryNonAtomicTestShaderCode;
- isStorageAS = true;
+ isGlobalSpace = true;
break;
case MemoryType.AtomicWorkgroupClass:
memoryTypeCode = workgroupMemoryAtomicTestShaderCode;
break;
case MemoryType.NonAtomicWorkgroupClass:
memoryTypeCode = workgroupMemoryNonAtomicTestShaderCode;
+ break;
+ case MemoryType.NonAtomicTextureClass:
+ memoryTypeCode = textureMemoryNonAtomicTestShaderCode;
+ isGlobalSpace = true;
+ break;
}
let testTypeCode;
switch (testType) {
@@ -1074,7 +1275,7 @@ export function buildTestShader(
testTypeCode = interWorkgroupTestShaderCode;
break;
case TestType.IntraWorkgroup:
- if (isStorageAS) {
+ if (isGlobalSpace) {
testTypeCode = storageIntraWorkgroupTestShaderCode;
} else {
testTypeCode = intraWorkgroupTestShaderCode;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/texture_intra_invocation_coherence.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/texture_intra_invocation_coherence.spec.ts
new file mode 100644
index 0000000000..1ab8c3d5b4
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/memory_model/texture_intra_invocation_coherence.spec.ts
@@ -0,0 +1,333 @@
+export const description = `
+Test that read/write storage textures are coherent within an invocation.
+
+Each invocation is assigned several random writing indices and a single
+read index from among those. Writes are randomly predicated (except the
+one corresponding to the read). Checks that an invocation can read data
+it has written to the texture previously.
+Does not test coherence between invocations
+
+Some platform (e.g. Metal) require a fence call to make writes visible
+to reads performed by the same invocation. These tests attempt to ensure
+WebGPU implementations emit correct fence calls.`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { unreachable, iterRange } from '../../../../common/util/util.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { PRNG } from '../../../util/prng.js';
+
+const kRWStorageFormats: GPUTextureFormat[] = ['r32uint', 'r32sint', 'r32float'];
+const kDimensions: GPUTextureViewDimension[] = ['1d', '2d', '2d-array', '3d'];
+
+export const g = makeTestGroup(GPUTest);
+
+function indexToCoord(dim: GPUTextureViewDimension): string {
+ switch (dim) {
+ case '1d': {
+ return `
+fn indexToCoord(idx : u32) -> u32 {
+ return idx;
+}`;
+ }
+ case '2d':
+ case '2d-array': {
+ return `
+fn indexToCoord(idx : u32) -> vec2u {
+ return vec2u(idx % (wgx * num_wgs_x), idx / (wgx * num_wgs_x));
+}`;
+ }
+ case '3d': {
+ return `
+fn indexToCoord(idx : u32) -> vec3u {
+ return vec3u(idx % (wgx * num_wgs_x), idx / (wgx * num_wgs_x), 0);
+}`;
+ }
+ default: {
+ unreachable(`unhandled dimension: ${dim}`);
+ }
+ }
+ return ``;
+}
+
+function textureType(format: GPUTextureFormat, dim: GPUTextureViewDimension): string {
+ let t = `texture_storage_`;
+ switch (dim) {
+ case '1d': {
+ t += '1d';
+ break;
+ }
+ case '2d': {
+ t += '2d';
+ break;
+ }
+ case '2d-array': {
+ t += '2d_array';
+ break;
+ }
+ case '3d': {
+ t += '3d';
+ break;
+ }
+ default: {
+ unreachable(`unhandled dim: ${dim}`);
+ }
+ }
+ t += `<${format}, read_write>`;
+ return t;
+}
+
+function textureStore(dim: GPUTextureViewDimension, index: string): string {
+ let code = `textureStore(t, indexToCoord(${index}), `;
+ if (dim === '2d-array') {
+ code += `0, `;
+ }
+ code += `texel)`;
+ return code;
+}
+
+function textureLoad(dim: GPUTextureViewDimension, format: GPUTextureFormat): string {
+ let code = `textureLoad(t, indexToCoord(read_index[global_index])`;
+ if (dim === '2d-array') {
+ code += `, 0`;
+ }
+ code += `).x`;
+ if (format !== 'r32uint') {
+ code = `u32(${code})`;
+ }
+ return code;
+}
+
+function texel(format: GPUTextureFormat): string {
+ switch (format) {
+ case 'r32uint': {
+ return 'vec4u(global_index,0,0,0)';
+ }
+ case 'r32sint': {
+ return 'vec4i(i32(global_index),0,0,0)';
+ }
+ case 'r32float': {
+ return 'vec4f(f32(global_index),0,0,0)';
+ }
+ default: {
+ unreachable('unhandled format: ${format}');
+ }
+ }
+ return '';
+}
+
+function getTextureSize(numTexels: number, dim: GPUTextureViewDimension): GPUExtent3D {
+ const size: GPUExtent3D = { width: 1, height: 1, depthOrArrayLayers: 1 };
+ switch (dim) {
+ case '1d': {
+ size.width = numTexels;
+ break;
+ }
+ case '2d':
+ case '2d-array':
+ case '3d': {
+ size.width = numTexels / 2;
+ size.height = numTexels / 2;
+ // depthOrArrayLayers defaults to 1
+ break;
+ }
+ default: {
+ unreachable(`unhandled dim: ${dim}`);
+ }
+ }
+ return size;
+}
+
+g.test('texture_intra_invocation_coherence')
+ .desc(`Tests writes from an invocation are visible to reads from the same invocation`)
+ .params(u => u.combine('format', kRWStorageFormats).combine('dim', kDimensions))
+ .beforeAllSubcases(t => {
+ t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
+ })
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+
+ const wgx = 16;
+ const wgy = t.device.limits.maxComputeInvocationsPerWorkgroup / wgx;
+ const num_wgs_x = 2;
+ const num_wgs_y = 2;
+ const invocations = wgx * wgy * num_wgs_x * num_wgs_y;
+ const num_writes_per_invocation = 4;
+
+ const code = `
+requires readonly_and_readwrite_storage_textures;
+
+@group(0) @binding(0)
+var t : ${textureType(t.params.format, t.params.dim)};
+
+@group(1) @binding(0)
+var<storage> write_indices : array<vec4u>;
+
+@group(1) @binding(1)
+var<storage> read_index : array<u32>;
+
+@group(1) @binding(2)
+var<storage> write_mask : array<vec4u>;
+
+@group(1) @binding(3)
+var<storage, read_write> output : array<u32>;
+
+const wgx = ${wgx}u;
+const wgy = ${wgy}u;
+const num_wgs_x = ${num_wgs_x}u;
+const num_wgs_y = ${num_wgs_y}u;
+
+${indexToCoord(t.params.dim)}
+
+@compute @workgroup_size(wgx, wgy, 1)
+fn main(@builtin(global_invocation_id) gid : vec3u) {
+ let global_index = gid.x + gid.y * num_wgs_x * wgx;
+
+ let write_index = write_indices[global_index];
+ let mask = write_mask[global_index];
+ let texel = ${texel(t.params.format)};
+
+ if mask.x != 0 {
+ ${textureStore(t.params.dim, 'write_index.x')};
+ }
+ if mask.y != 0 {
+ ${textureStore(t.params.dim, 'write_index.y')};
+ }
+ if mask.z != 0 {
+ ${textureStore(t.params.dim, 'write_index.z')};
+ }
+ if mask.w != 0 {
+ ${textureStore(t.params.dim, 'write_index.w')};
+ }
+ output[global_index] = ${textureLoad(t.params.dim, t.params.format)};
+}`;
+
+ // To get a variety of testing, seed the random number generator based on which case this is.
+ // This means subcases will not execute the same code.
+ const seed =
+ kRWStorageFormats.indexOf(t.params.format) * kRWStorageFormats.length +
+ kDimensions.indexOf(t.params.dim);
+ const prng = new PRNG(seed);
+
+ const num_write_indices = invocations * num_writes_per_invocation;
+ const write_indices = new Uint32Array([...iterRange(num_write_indices, x => x)]);
+ const write_masks = new Uint32Array([...iterRange(num_write_indices, x => 0)]);
+ // Shuffle the indices.
+ for (let i = 0; i < num_write_indices; i++) {
+ const remaining = num_write_indices - i;
+ const swapIdx = (prng.randomU32() % remaining) + i;
+ const tmp = write_indices[swapIdx];
+ write_indices[swapIdx] = write_indices[i];
+ write_indices[i] = tmp;
+
+ // Assign random write masks
+ const mask = prng.randomU32() % 2;
+ write_masks[i] = mask;
+ }
+ const num_read_indices = invocations;
+ const read_indices = new Uint32Array(num_read_indices);
+ for (let i = 0; i < num_read_indices; i++) {
+ // Pick a random index from index from this invocation's writes to read from.
+ // Ensure that write is not masked out.
+ const readIdx = prng.randomU32() % num_writes_per_invocation;
+ read_indices[i] = write_indices[num_writes_per_invocation * i + readIdx];
+ write_masks[num_writes_per_invocation * i + readIdx] = 1;
+ }
+
+ // Buffers
+ const write_index_buffer = t.makeBufferWithContents(
+ write_indices,
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ );
+ t.trackForCleanup(write_index_buffer);
+ const read_index_buffer = t.makeBufferWithContents(
+ read_indices,
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ );
+ t.trackForCleanup(read_index_buffer);
+ const write_mask_buffer = t.makeBufferWithContents(
+ write_masks,
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE
+ );
+ t.trackForCleanup(write_mask_buffer);
+ const output_buffer = t.makeBufferWithContents(
+ new Uint32Array([...iterRange(invocations, x => 0)]),
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE
+ );
+ t.trackForCleanup(output_buffer);
+
+ // Texture
+ const texture_size = getTextureSize(invocations * num_writes_per_invocation, t.params.dim);
+ const texture = t.device.createTexture({
+ format: t.params.format,
+ dimension: t.params.dim === '2d-array' ? '2d' : (t.params.dim as GPUTextureDimension),
+ size: texture_size,
+ usage: GPUTextureUsage.STORAGE_BINDING,
+ });
+ t.trackForCleanup(texture);
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code,
+ }),
+ entryPoint: 'main',
+ },
+ });
+
+ const bg0 = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: texture.createView({
+ format: t.params.format,
+ dimension: t.params.dim,
+ mipLevelCount: 1,
+ arrayLayerCount: 1,
+ }),
+ },
+ ],
+ });
+ const bg1 = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(1),
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: write_index_buffer,
+ },
+ },
+ {
+ binding: 1,
+ resource: {
+ buffer: read_index_buffer,
+ },
+ },
+ {
+ binding: 2,
+ resource: {
+ buffer: write_mask_buffer,
+ },
+ },
+ {
+ binding: 3,
+ resource: {
+ buffer: output_buffer,
+ },
+ },
+ ],
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bg0);
+ pass.setBindGroup(1, bg1);
+ pass.dispatchWorkgroups(num_wgs_x, num_wgs_y, 1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ const expectedOutput = new Uint32Array([...iterRange(num_read_indices, x => x)]);
+ t.expectGPUBufferValuesEqual(output_buffer, expectedOutput);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access.spec.ts
index 965dd283dd..70d3438fcf 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access.spec.ts
@@ -7,6 +7,7 @@ TODO: add tests to check that textureLoad operations stay in-bounds.
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { assert } from '../../../common/util/util.js';
+import { Float16Array } from '../../../external/petamoriken/float16/float16.js';
import { GPUTest } from '../../gpu_test.js';
import { align } from '../../util/math.js';
import { generateTypes, supportedScalarTypes, supportsAtomics } from '../types.js';
@@ -25,6 +26,7 @@ const kMinI32 = -0x8000_0000;
*/
async function runShaderTest(
t: GPUTest,
+ enables: string,
stage: GPUShaderStageFlags,
testSource: string,
layout: GPUPipelineLayout,
@@ -41,7 +43,7 @@ async function runShaderTest(
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE,
});
- const source = `
+ const source = `${enables}
struct Constants {
zero: u32
};
@@ -96,10 +98,12 @@ fn main() {
/** Fill an ArrayBuffer with sentinel values, except clear a region to zero. */
function testFillArrayBuffer(
array: ArrayBuffer,
- type: 'u32' | 'i32' | 'f32',
+ type: 'u32' | 'i32' | 'f16' | 'f32',
{ zeroByteStart, zeroByteCount }: { zeroByteStart: number; zeroByteCount: number }
) {
- const constructor = { u32: Uint32Array, i32: Int32Array, f32: Float32Array }[type];
+ const constructor = { u32: Uint32Array, i32: Int32Array, f16: Float16Array, f32: Float32Array }[
+ type
+ ];
assert(zeroByteCount % constructor.BYTES_PER_ELEMENT === 0);
new constructor(array).fill(42);
new constructor(array, zeroByteStart, zeroByteCount / constructor.BYTES_PER_ELEMENT).fill(0);
@@ -122,6 +126,7 @@ g.test('linear_memory')
TODO: Test types like vec2<atomic<i32>>, if that's allowed.
TODO: Test exprIndexAddon as constexpr.
TODO: Test exprIndexAddon as pipeline-overridable constant expression.
+ TODO: Adjust test logic to support array of f16 in the uniform address space
`
)
.params(u =>
@@ -168,10 +173,15 @@ g.test('linear_memory')
{ shadowingMode: 'function-scope' },
])
.expand('isAtomic', p => (supportsAtomics(p) ? [false, true] : [false]))
- .beginSubcases()
.expand('baseType', supportedScalarTypes)
+ .beginSubcases()
.expandWithParams(generateTypes)
)
+ .beforeAllSubcases(t => {
+ if (t.params.baseType === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
.fn(async t => {
const {
addressSpace,
@@ -189,6 +199,13 @@ g.test('linear_memory')
assert(_kTypeInfo !== undefined, 'not an indexable type');
assert('arrayLength' in _kTypeInfo);
+ if (baseType === 'f16' && addressSpace === 'uniform' && containerType === 'array') {
+ // Array elements must be aligned to 16 bytes, but the logic in generateTypes
+ // creates an array of vec4 of the baseType. But for f16 that's only 8 bytes.
+ // We would need to write more complex logic for that.
+ t.skip('Test logic does not handle array of f16 in the uniform address space');
+ }
+
let usesCanary = false;
let globalSource = '';
let testFunctionSource = '';
@@ -429,6 +446,8 @@ fn runTest() -> u32 {
],
});
+ const enables = t.params.baseType === 'f16' ? 'enable f16;' : '';
+
// Run it.
if (bufferBindingSize !== undefined && baseType !== 'bool') {
const expectedData = new ArrayBuffer(testBufferSize);
@@ -450,6 +469,7 @@ fn runTest() -> u32 {
// Run the shader, accessing the buffer.
await runShaderTest(
t,
+ enables,
GPUShaderStage.COMPUTE,
testSource,
layout,
@@ -475,6 +495,6 @@ fn runTest() -> u32 {
bufferBindingEnd
);
} else {
- await runShaderTest(t, GPUShaderStage.COMPUTE, testSource, layout, []);
+ await runShaderTest(t, enables, GPUShaderStage.COMPUTE, testSource, layout, []);
}
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access_vertex.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access_vertex.spec.ts
index de90301592..32a4c243df 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access_vertex.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/robust_access_vertex.spec.ts
@@ -545,6 +545,7 @@ g.test('vertex_buffer_access')
.combine('additionalBuffers', [0, 4])
.combine('partialLastNumber', [false, true])
.combine('offsetVertexBuffer', [false, true])
+ .beginSubcases()
.combine('errorScale', [0, 1, 4, 10 ** 2, 10 ** 4, 10 ** 6])
.unless(p => p.drawCallTestParameter === 'instanceCount' && p.errorScale > 10 ** 4) // To avoid timeout
)
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts
index fcf3159c64..a40b426332 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/compute_builtins.spec.ts
@@ -1,7 +1,6 @@
export const description = `Test compute shader builtin variables`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
-import { iterRange } from '../../../../common/util/util.js';
import { GPUTest } from '../../../gpu_test.js';
export const g = makeTestGroup(GPUTest);
@@ -98,17 +97,14 @@ g.test('inputs')
// WGSL shader that stores every builtin value to a buffer, for every invocation in the grid.
const wgsl = `
- struct S {
- data : array<u32>
+ struct Outputs {
+ local_id: vec3u,
+ local_index: u32,
+ global_id: vec3u,
+ group_id: vec3u,
+ num_groups: vec3u,
};
- struct V {
- data : array<vec3<u32>>
- };
- @group(0) @binding(0) var<storage, read_write> local_id_out : V;
- @group(0) @binding(1) var<storage, read_write> local_index_out : S;
- @group(0) @binding(2) var<storage, read_write> global_id_out : V;
- @group(0) @binding(3) var<storage, read_write> group_id_out : V;
- @group(0) @binding(4) var<storage, read_write> num_groups_out : V;
+ @group(0) @binding(0) var<storage, read_write> outputs : array<Outputs>;
${structures}
@@ -122,11 +118,13 @@ g.test('inputs')
) {
let group_index = ((${group_id}.z * ${num_groups}.y) + ${group_id}.y) * ${num_groups}.x + ${group_id}.x;
let global_index = group_index * ${invocationsPerGroup}u + ${local_index};
- local_id_out.data[global_index] = ${local_id};
- local_index_out.data[global_index] = ${local_index};
- global_id_out.data[global_index] = ${global_id};
- group_id_out.data[global_index] = ${group_id};
- num_groups_out.data[global_index] = ${num_groups};
+ var o: Outputs;
+ o.local_id = ${local_id};
+ o.local_index = ${local_index};
+ o.global_id = ${global_id};
+ o.group_id = ${group_id};
+ o.num_groups = ${num_groups};
+ outputs[global_index] = o;
}
`;
@@ -140,35 +138,24 @@ g.test('inputs')
},
});
- // Helper to create a `size`-byte buffer with binding number `binding`.
- function createBuffer(size: number, binding: number) {
- const buffer = t.device.createBuffer({
- size,
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
- });
- t.trackForCleanup(buffer);
-
- bindGroupEntries.push({
- binding,
- resource: {
- buffer,
- },
- });
-
- return buffer;
- }
+ // Offsets are in u32 size units
+ const kLocalIdOffset = 0;
+ const kLocalIndexOffset = 3;
+ const kGlobalIdOffset = 4;
+ const kGroupIdOffset = 8;
+ const kNumGroupsOffset = 12;
+ const kOutputElementSize = 16;
// Create the output buffers.
- const bindGroupEntries: GPUBindGroupEntry[] = [];
- const localIdBuffer = createBuffer(totalInvocations * 16, 0);
- const localIndexBuffer = createBuffer(totalInvocations * 4, 1);
- const globalIdBuffer = createBuffer(totalInvocations * 16, 2);
- const groupIdBuffer = createBuffer(totalInvocations * 16, 3);
- const numGroupsBuffer = createBuffer(totalInvocations * 16, 4);
+ const outputBuffer = t.device.createBuffer({
+ size: totalInvocations * kOutputElementSize * 4,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(outputBuffer);
const bindGroup = t.device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
- entries: bindGroupEntries,
+ entries: [{ binding: 0, resource: { buffer: outputBuffer } }],
});
// Run the shader.
@@ -204,11 +191,7 @@ g.test('inputs')
// Helper to check that the vec3<u32> value at each index of the provided `output` buffer
// matches the expected value for that invocation, as generated by the `getBuiltinValue`
// function. The `name` parameter is the builtin name, used for error messages.
- const checkEachIndex = (
- output: Uint32Array,
- name: string,
- getBuiltinValue: (groupId: vec3, localId: vec3) => vec3
- ) => {
+ const checkEachIndex = (output: Uint32Array) => {
// Loop over workgroups.
for (let gz = 0; gz < t.params.numGroups.z; gz++) {
for (let gy = 0; gy < t.params.numGroups.y; gy++) {
@@ -220,30 +203,44 @@ g.test('inputs')
const groupIndex = (gz * t.params.numGroups.y + gy) * t.params.numGroups.x + gx;
const localIndex = (lz * t.params.groupSize.y + ly) * t.params.groupSize.x + lx;
const globalIndex = groupIndex * invocationsPerGroup + localIndex;
- const expected = getBuiltinValue(
- { x: gx, y: gy, z: gz },
- { x: lx, y: ly, z: lz }
- );
- if (output[globalIndex * 4 + 0] !== expected.x) {
- return new Error(
- `${name}.x failed at group(${gx},${gy},${gz}) local(${lx},${ly},${lz}))\n` +
- ` expected: ${expected.x}\n` +
- ` got: ${output[globalIndex * 4 + 0]}`
- );
- }
- if (output[globalIndex * 4 + 1] !== expected.y) {
- return new Error(
- `${name}.y failed at group(${gx},${gy},${gz}) local(${lx},${ly},${lz}))\n` +
- ` expected: ${expected.y}\n` +
- ` got: ${output[globalIndex * 4 + 1]}`
+ const globalOffset = globalIndex * kOutputElementSize;
+
+ const expectEqual = (name: string, expected: number, actual: number) => {
+ if (actual !== expected) {
+ return new Error(
+ `${name} failed at group(${gx},${gy},${gz}) local(${lx},${ly},${lz}))\n` +
+ ` expected: ${expected}\n` +
+ ` got: ${actual}`
+ );
+ }
+ return undefined;
+ };
+
+ const checkVec3Value = (name: string, fieldOffset: number, expected: vec3) => {
+ const offset = globalOffset + fieldOffset;
+ return (
+ expectEqual(`${name}.x`, expected.x, output[offset + 0]) ||
+ expectEqual(`${name}.y`, expected.y, output[offset + 1]) ||
+ expectEqual(`${name}.z`, expected.z, output[offset + 2])
);
- }
- if (output[globalIndex * 4 + 2] !== expected.z) {
- return new Error(
- `${name}.z failed at group(${gx},${gy},${gz}) local(${lx},${ly},${lz}))\n` +
- ` expected: ${expected.z}\n` +
- ` got: ${output[globalIndex * 4 + 2]}`
+ };
+
+ const error =
+ checkVec3Value('local_id', kLocalIdOffset, { x: lx, y: ly, z: lz }) ||
+ checkVec3Value('global_id', kGlobalIdOffset, {
+ x: gx * t.params.groupSize.x + lx,
+ y: gy * t.params.groupSize.y + ly,
+ z: gz * t.params.groupSize.z + lz,
+ }) ||
+ checkVec3Value('group_id', kGroupIdOffset, { x: gx, y: gy, z: gz }) ||
+ checkVec3Value('num_groups', kNumGroupsOffset, t.params.numGroups) ||
+ expectEqual(
+ 'local_index',
+ localIndex,
+ output[globalOffset + kLocalIndexOffset]
);
+ if (error) {
+ return error;
}
}
}
@@ -254,44 +251,8 @@ g.test('inputs')
return undefined;
};
- // Check @builtin(local_invocation_index) values.
- t.expectGPUBufferValuesEqual(
- localIndexBuffer,
- new Uint32Array([...iterRange(totalInvocations, x => x % invocationsPerGroup)])
- );
-
- // Check @builtin(local_invocation_id) values.
- t.expectGPUBufferValuesPassCheck(
- localIdBuffer,
- outputData => checkEachIndex(outputData, 'local_invocation_id', (_, localId) => localId),
- { type: Uint32Array, typedLength: totalInvocations * 4 }
- );
-
- // Check @builtin(global_invocation_id) values.
- const getGlobalId = (groupId: vec3, localId: vec3) => {
- return {
- x: groupId.x * t.params.groupSize.x + localId.x,
- y: groupId.y * t.params.groupSize.y + localId.y,
- z: groupId.z * t.params.groupSize.z + localId.z,
- };
- };
- t.expectGPUBufferValuesPassCheck(
- globalIdBuffer,
- outputData => checkEachIndex(outputData, 'global_invocation_id', getGlobalId),
- { type: Uint32Array, typedLength: totalInvocations * 4 }
- );
-
- // Check @builtin(workgroup_id) values.
- t.expectGPUBufferValuesPassCheck(
- groupIdBuffer,
- outputData => checkEachIndex(outputData, 'workgroup_id', (groupId, _) => groupId),
- { type: Uint32Array, typedLength: totalInvocations * 4 }
- );
-
- // Check @builtin(num_workgroups) values.
- t.expectGPUBufferValuesPassCheck(
- numGroupsBuffer,
- outputData => checkEachIndex(outputData, 'num_workgroups', () => t.params.numGroups),
- { type: Uint32Array, typedLength: totalInvocations * 4 }
- );
+ t.expectGPUBufferValuesPassCheck(outputBuffer, outputData => checkEachIndex(outputData), {
+ type: Uint32Array,
+ typedLength: outputBuffer.size / 4,
+ });
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts
new file mode 100644
index 0000000000..2bb03dab8d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/fragment_builtins.spec.ts
@@ -0,0 +1,1410 @@
+export const description = `Test fragment shader builtin variables and inter-stage variables
+
+* test builtin(position)
+* test @interpolate
+* test builtin(sample_index)
+* test builtin(front_facing)
+* test builtin(sample_mask)
+
+Note: @interpolate settings and sample_index affect whether or not the fragment shader
+is evaluated per-fragment or per-sample. With @interpolate(, sample) or usage of
+@builtin(sample_index) the fragment shader should be executed per-sample.
+
+* sample_mask output is tested in
+ src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts
+
+* frag_depth output is tested in
+ src/webgpu/api/operation/rendering/depth_clip_clamp.spec.ts
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { ErrorWithExtra, assert, range, unreachable } from '../../../../common/util/util.js';
+import { InterpolationSampling, InterpolationType } from '../../../constants.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { getMultisampleFragmentOffsets } from '../../../multisample_info.js';
+import { dotProduct, subtractVectors } from '../../../util/math.js';
+import { TexelView } from '../../../util/texture/texel_view.js';
+import { findFailedPixels } from '../../../util/texture/texture_ok.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const s_deviceToPipelineMap = new WeakMap<
+ GPUDevice,
+ {
+ texture_2d?: GPUComputePipeline;
+ texture_multisampled_2d?: GPUComputePipeline;
+ }
+>();
+
+/**
+ * Returns an object of pipelines associated
+ * by weakmap to a device so we can cache pipelines.
+ */
+function getPipelinesForDevice(device: GPUDevice) {
+ let pipelines = s_deviceToPipelineMap.get(device);
+ if (!pipelines) {
+ pipelines = {};
+ s_deviceToPipelineMap.set(device, pipelines);
+ }
+ return pipelines;
+}
+
+/**
+ * Gets a compute pipeline that will copy the given texture if passed
+ * a dispatch size of texture.width, texture.height
+ * @param device a device
+ * @param texture texture the pipeline is needed for.
+ * @returns A GPUComputePipeline
+ */
+function getCopyMultisamplePipelineForDevice(device: GPUDevice, textures: GPUTexture[]) {
+ assert(textures.length === 4);
+ assert(textures[0].sampleCount === textures[1].sampleCount);
+ assert(textures[0].sampleCount === textures[2].sampleCount);
+ assert(textures[0].sampleCount === textures[3].sampleCount);
+
+ const pipelineType = textures[0].sampleCount > 1 ? 'texture_multisampled_2d' : 'texture_2d';
+ const pipelines = getPipelinesForDevice(device);
+ let pipeline = pipelines[pipelineType];
+ if (!pipeline) {
+ const isMultisampled = pipelineType === 'texture_multisampled_2d';
+ const numSamples = isMultisampled ? 'textureNumSamples(texture0)' : '1u';
+ const sampleIndex = isMultisampled ? 'sampleIndex' : '0';
+ const module = device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var texture0: ${pipelineType}<f32>;
+ @group(0) @binding(1) var texture1: ${pipelineType}<f32>;
+ @group(0) @binding(2) var texture2: ${pipelineType}<f32>;
+ @group(0) @binding(3) var texture3: ${pipelineType}<f32>;
+ @group(0) @binding(4) var<storage, read_write> buffer: array<f32>;
+
+ @compute @workgroup_size(1) fn cs(@builtin(global_invocation_id) id: vec3u) {
+ let numSamples = ${numSamples};
+ let dimensions = textureDimensions(texture0);
+ let sampleIndex = id.x % numSamples;
+ let tx = id.x / numSamples;
+ let offset = ((id.y * dimensions.x + tx) * numSamples + sampleIndex) * 4;
+ let r = vec4u(textureLoad(texture0, vec2u(tx, id.y), ${sampleIndex}) * 255.0);
+ let g = vec4u(textureLoad(texture1, vec2u(tx, id.y), ${sampleIndex}) * 255.0);
+ let b = vec4u(textureLoad(texture2, vec2u(tx, id.y), ${sampleIndex}) * 255.0);
+ let a = vec4u(textureLoad(texture3, vec2u(tx, id.y), ${sampleIndex}) * 255.0);
+
+ // expand rgba8unorm values back to their byte form, add them together
+ // and cast them to an f32 so we can recover the f32 values we encoded
+ // in the rgba8unorm texture.
+ buffer[offset + 0] = bitcast<f32>(dot(r, vec4u(0x1000000, 0x10000, 0x100, 0x1)));
+ buffer[offset + 1] = bitcast<f32>(dot(g, vec4u(0x1000000, 0x10000, 0x100, 0x1)));
+ buffer[offset + 2] = bitcast<f32>(dot(b, vec4u(0x1000000, 0x10000, 0x100, 0x1)));
+ buffer[offset + 3] = bitcast<f32>(dot(a, vec4u(0x1000000, 0x10000, 0x100, 0x1)));
+ }
+ `,
+ });
+
+ pipeline = device.createComputePipeline({
+ label: 'copy multisampled texture pipeline',
+ layout: 'auto',
+ compute: {
+ module,
+ entryPoint: 'cs',
+ },
+ });
+
+ pipelines[pipelineType] = pipeline;
+ }
+ return pipeline;
+}
+
+function isTextureSameDimensions(a: GPUTexture, b: GPUTexture) {
+ return (
+ a.sampleCount === b.sampleCount &&
+ a.width === b.width &&
+ a.height === b.height &&
+ a.depthOrArrayLayers === b.depthOrArrayLayers
+ );
+}
+
+/**
+ * Copies a texture (even if multisampled) to a buffer
+ * @param t a gpu test
+ * @param texture texture to copy
+ * @returns buffer with copy of texture, mip level 0, array layer 0.
+ */
+function copyRGBA8EncodedFloatTexturesToBufferIncludingMultisampledTextures(
+ t: GPUTest,
+ textures: GPUTexture[]
+) {
+ assert(textures.length === 4);
+ assert(isTextureSameDimensions(textures[0], textures[1]));
+ assert(isTextureSameDimensions(textures[0], textures[2]));
+ assert(isTextureSameDimensions(textures[0], textures[3]));
+ const { width, height, sampleCount } = textures[0];
+
+ const copyBuffer = t.device.createBuffer({
+ size: width * height * sampleCount * 4 * 4,
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+ t.trackForCleanup(copyBuffer);
+
+ const buffer = t.device.createBuffer({
+ size: copyBuffer.size,
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
+ });
+ t.trackForCleanup(buffer);
+
+ const pipeline = getCopyMultisamplePipelineForDevice(t.device, textures);
+ const encoder = t.device.createCommandEncoder();
+
+ const textureEntries = textures.map(
+ (texture, i) => ({ binding: i, resource: texture.createView() }) as GPUBindGroupEntry
+ );
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [...textureEntries, { binding: 4, resource: { buffer: copyBuffer } }],
+ });
+
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(width * sampleCount, height);
+ pass.end();
+
+ encoder.copyBufferToBuffer(copyBuffer, 0, buffer, 0, buffer.size);
+
+ t.device.queue.submit([encoder.finish()]);
+
+ return buffer;
+}
+
+/* column constants */
+const kX = 0;
+const kY = 1;
+const kZ = 2;
+const kW = 3;
+
+/**
+ * Gets a column of values from an array of arrays.
+ */
+function getColumn(values: readonly number[][], colNum: number) {
+ return values.map(v => v[colNum]);
+}
+
+/**
+ * Computes the linear interpolation of 3 values from 3 vertices of a triangle
+ * based on barycentric coordinates
+ */
+function linearInterpolation(baryCoords: readonly number[], interCoords: readonly number[]) {
+ return dotProduct(baryCoords, interCoords);
+}
+
+/**
+ * Computes the perspective interpolation of 3 values from 3 vertices of a
+ * triangle based on barycentric coordinates and their corresponding clip space
+ * W coordinates.
+ */
+function perspectiveInterpolation(
+ barycentricCoords: readonly number[],
+ clipSpaceTriangleCoords: readonly number[][],
+ interCoords: readonly number[]
+) {
+ const [a, b, c] = barycentricCoords;
+ const [fa, fb, fc] = interCoords;
+ const wa = clipSpaceTriangleCoords[0][kW];
+ const wb = clipSpaceTriangleCoords[1][kW];
+ const wc = clipSpaceTriangleCoords[2][kW];
+
+ return ((a * fa) / wa + (b * fb) / wb + (c * fc) / wc) / (a / wa + b / wb + c / wc);
+}
+
+/**
+ * Converts clip space coords to NDC coords
+ */
+function clipSpaceToNDC(point: readonly number[]) {
+ return point.map(v => v / point[kW]);
+}
+
+/**
+ * Converts NDC coords to window coords.
+ */
+function ndcToWindow(ndcPoint: readonly number[], viewport: readonly number[]) {
+ const [xd, yd, zd] = ndcPoint;
+ const px = viewport[2];
+ const py = viewport[3];
+ const ox = viewport[0] + px / 2;
+ const oy = viewport[1] + py / 2;
+ const zNear = viewport[4];
+ const zFar = viewport[5];
+ // prettier-ignore
+ return [
+ px / 2 * xd + ox,
+ -py / 2 * yd + oy,
+ zd * (zFar - zNear) + zNear,
+ ];
+}
+
+/**
+ * Computes barycentric coordinates of triangle for point p.
+ * @param trianglePoints points for triangle
+ * @param p point in triangle (or relative to it)
+ * @returns barycentric coords of p
+ */
+function calcBarycentricCoordinates(trianglePoints: number[][], p: number[]) {
+ const [a, b, c] = trianglePoints;
+
+ const v0 = subtractVectors(b, a);
+ const v1 = subtractVectors(c, a);
+ const v2 = subtractVectors(p, a);
+
+ const dot00 = dotProduct(v0, v0);
+ const dot01 = dotProduct(v0, v1);
+ const dot11 = dotProduct(v1, v1);
+ const dot20 = dotProduct(v2, v0);
+ const dot21 = dotProduct(v2, v1);
+
+ const denom = 1 / (dot00 * dot11 - dot01 * dot01);
+ const v = (dot11 * dot20 - dot01 * dot21) * denom;
+ const w = (dot00 * dot21 - dot01 * dot20) * denom;
+ const u = 1 - v - w;
+
+ return [u, v, w];
+}
+
+/**
+ * Returns true if point is inside triangle
+ */
+function isInsideTriangle(barycentricCoords: number[]) {
+ for (const v of barycentricCoords) {
+ if (v < 0 || v > 1) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Returns true if windowPoints define a clockwise triangle
+ */
+function isTriangleClockwise(windowPoints: readonly number[][]) {
+ let sum = 0;
+ for (let i = 0; i < 3; ++i) {
+ const p0 = windowPoints[i];
+ const p1 = windowPoints[(i + 1) % 3];
+ sum += p0[kX] * p1[kY] - p1[kX] * p0[kY];
+ }
+ return sum >= 0;
+}
+
+type FragData = {
+ baseVertexIndex: number;
+ fragmentPoint: readonly number[];
+ fragmentBarycentricCoords: readonly number[];
+ sampleBarycentricCoords: readonly number[];
+ clipSpacePoints: readonly number[][];
+ ndcPoints: readonly number[][];
+ windowPoints: readonly number[][];
+ sampleIndex: number;
+ sampleMask: number;
+ frontFacing: boolean;
+};
+
+/**
+ * For each sample in texture, computes the values that would be provided
+ * to the shader as `@builtin(position)` if the texture was a render target
+ * and every point in the texture was inside the triangle.
+ * @param texture The texture
+ * @param clipSpacePoints triangle points in clip space
+ * @returns the expected values for each sample
+ */
+function generateFragmentInputs({
+ width,
+ height,
+ nearFar,
+ sampleCount,
+ frontFace,
+ clipSpacePoints,
+ interpolateFn,
+}: {
+ width: number;
+ height: number;
+ nearFar: readonly number[];
+ sampleCount: number;
+ frontFace?: GPUFrontFace;
+ clipSpacePoints: readonly number[][];
+ interpolateFn: (fragData: FragData) => number[];
+}) {
+ const expected = new Float32Array(width * height * sampleCount * 4);
+
+ const viewport = [0, 0, width, height, ...nearFar];
+
+ // For each triangle
+ for (let vertexIndex = 0; vertexIndex < clipSpacePoints.length; vertexIndex += 3) {
+ const ndcPoints = clipSpacePoints.slice(vertexIndex, vertexIndex + 3).map(clipSpaceToNDC);
+ const windowPoints = ndcPoints.map(p => ndcToWindow(p, viewport));
+ const windowPoints2D = windowPoints.map(p => p.slice(0, 2));
+
+ const cw = isTriangleClockwise(windowPoints2D);
+ const frontFacing = frontFace === 'cw' ? cw : !cw;
+ const fragmentOffsets = getMultisampleFragmentOffsets(sampleCount)!;
+
+ for (let y = 0; y < height; ++y) {
+ for (let x = 0; x < width; ++x) {
+ let sampleMask = 0;
+ for (let sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) {
+ const localSampleMask = 1 << sampleIndex;
+ const multisampleOffset = fragmentOffsets[sampleIndex];
+ const sampleFragmentPoint = [x + multisampleOffset[0], y + multisampleOffset[1]];
+ const sampleBarycentricCoords = calcBarycentricCoordinates(
+ windowPoints2D,
+ sampleFragmentPoint
+ );
+
+ const inside = isInsideTriangle(sampleBarycentricCoords);
+ if (inside) {
+ sampleMask |= localSampleMask;
+ }
+ }
+
+ for (let sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) {
+ const fragmentPoint = [x + 0.5, y + 0.5];
+ const multisampleOffset = fragmentOffsets[sampleIndex];
+ const sampleFragmentPoint = [x + multisampleOffset[0], y + multisampleOffset[1]];
+ const fragmentBarycentricCoords = calcBarycentricCoordinates(
+ windowPoints2D,
+ fragmentPoint
+ );
+ const sampleBarycentricCoords = calcBarycentricCoordinates(
+ windowPoints2D,
+ sampleFragmentPoint
+ );
+
+ const inside = isInsideTriangle(sampleBarycentricCoords);
+ if (inside) {
+ const output = interpolateFn({
+ baseVertexIndex: vertexIndex,
+ fragmentPoint,
+ fragmentBarycentricCoords,
+ sampleBarycentricCoords,
+ clipSpacePoints,
+ ndcPoints,
+ windowPoints,
+ sampleIndex,
+ sampleMask,
+ frontFacing,
+ });
+
+ const offset = ((y * width + x) * sampleCount + sampleIndex) * 4;
+ expected.set(output, offset);
+ }
+ }
+ }
+ }
+ }
+ return expected;
+}
+
+/**
+ * Computes 'builtin(position)`
+ */
+function computeFragmentPosition({
+ fragmentPoint,
+ fragmentBarycentricCoords,
+ clipSpacePoints,
+ windowPoints,
+}: FragData) {
+ return [
+ fragmentPoint[0],
+ fragmentPoint[1],
+ linearInterpolation(fragmentBarycentricCoords, getColumn(windowPoints, kZ)),
+ 1 /
+ perspectiveInterpolation(
+ fragmentBarycentricCoords,
+ clipSpacePoints,
+ getColumn(clipSpacePoints, kW)
+ ),
+ ];
+}
+
+/**
+ * Creates a function that will compute the interpolation of an inter-stage variable.
+ */
+function createInterStageInterpolationFn(
+ interStagePoints: number[][],
+ type: InterpolationType,
+ sampling: InterpolationSampling | undefined
+) {
+ return function ({
+ baseVertexIndex,
+ fragmentBarycentricCoords,
+ sampleBarycentricCoords,
+ clipSpacePoints,
+ }: FragData) {
+ const triangleInterStagePoints = interStagePoints.slice(baseVertexIndex, baseVertexIndex + 3);
+ const barycentricCoords =
+ sampling === 'center' ? fragmentBarycentricCoords : sampleBarycentricCoords;
+ switch (type) {
+ case 'perspective':
+ return triangleInterStagePoints[0].map((_, colNum: number) =>
+ perspectiveInterpolation(
+ barycentricCoords,
+ clipSpacePoints,
+ getColumn(triangleInterStagePoints, colNum)
+ )
+ );
+ break;
+ case 'linear':
+ return triangleInterStagePoints[0].map((_, colNum: number) =>
+ linearInterpolation(barycentricCoords, getColumn(triangleInterStagePoints, colNum))
+ );
+ break;
+ case 'flat':
+ return triangleInterStagePoints[0];
+ break;
+ default:
+ unreachable();
+ }
+ };
+}
+
+/**
+ * Creates a function that will compute the interpolation of an inter-stage variable
+ * and then return [1, 0, 0, 0] if all interpolated values are between 0.0 and 1.0 inclusive
+ * or [-1, 0, 0, 0] otherwise.
+ */
+function createInterStageInterpolationBetween0And1TestFn(
+ interStagePoints: number[][],
+ type: InterpolationType,
+ sampling: InterpolationSampling | undefined
+) {
+ const interpolateFn = createInterStageInterpolationFn(interStagePoints, type, sampling);
+ return function (fragData: FragData) {
+ const interpolatedValues = interpolateFn(fragData);
+ const allTrue = interpolatedValues.reduce((all, v) => all && v >= 0 && v <= 1, true);
+ return [allTrue ? 1 : -1, 0, 0, 0];
+ };
+}
+
+/**
+ * Computes 'builtin(sample_index)'
+ */
+function computeFragmentSampleIndex({ sampleIndex }: FragData) {
+ return [sampleIndex, 0, 0, 0];
+}
+
+/**
+ * Computes 'builtin(front_facing)'
+ */
+function computeFragmentFrontFacing({ frontFacing }: FragData) {
+ return [frontFacing ? 1 : 0, 0, 0, 0];
+}
+
+/**
+ * Computes 'builtin(sample_mask)'
+ */
+function computeSampleMask({ sampleMask }: FragData) {
+ return [sampleMask, 0, 0, 0];
+}
+
+/**
+ * Renders float32 fragment shader inputs values to 4 rgba8unorm textures that
+ * can be multisampled textures. It stores each of the channels, r, g, b, a of
+ * the shader input to a separate texture, doing the math required to store the
+ * float32 value into an rgba8unorm texel.
+ *
+ * Note: We could try to store the output to an vec4f storage buffer.
+ * Unfortunately, using a storage buffer has the issue that we need to compute
+ * an index with the very thing we're trying to test. Similarly, if we used a
+ * storage texture we would need to compute texture locations with the things
+ * we're trying to test. Also, using a storage buffer seems to affect certain
+ * backends like M1 Mac so it seems better to stick to rgba8unorm here and test
+ * using a storage buffer in a fragment shader separately.
+ *
+ * We can't use rgba32float because it's optional. We can't use rgba16float
+ * because it's optional in compat. We can't we use rgba32uint as that can't be
+ * multisampled.
+ */
+async function renderFragmentShaderInputsTo4TexturesAndReadbackValues(
+ t: GPUTest,
+ {
+ interpolationType,
+ interpolationSampling,
+ sampleCount,
+ width,
+ height,
+ nearFar,
+ frontFace,
+ clipSpacePoints,
+ interStagePoints,
+ fragInCode,
+ outputCode,
+ }: {
+ interpolationType: InterpolationType;
+ interpolationSampling?: InterpolationSampling;
+ width: number;
+ height: number;
+ sampleCount: number;
+ frontFace?: GPUFrontFace;
+ nearFar: readonly number[];
+ clipSpacePoints: readonly number[][];
+ interStagePoints: readonly number[][];
+ fragInCode: string;
+ outputCode: string;
+ }
+) {
+ const interpolate = `${interpolationType}${
+ interpolationSampling ? `, ${interpolationSampling}` : ''
+ }`;
+ const module = t.device.createShaderModule({
+ code: `
+ struct Uniforms {
+ resolution: vec2f,
+ };
+
+ @group(0) @binding(0) var<uniform> uni: Uniforms;
+
+ struct VertexOut {
+ @builtin(position) position: vec4f,
+ @location(0) @interpolate(${interpolate}) interpolatedValue: vec4f,
+ };
+
+ @vertex fn vs(@builtin(vertex_index) vNdx: u32) -> VertexOut {
+ let pos = array(
+ ${clipSpacePoints.map(p => `vec4f(${p.join(', ')})`).join(', ')}
+ );
+ let interStage = array(
+ ${interStagePoints.map(p => `vec4f(${p.join(', ')})`).join(', ')}
+ );
+ var v: VertexOut;
+ v.position = pos[vNdx];
+ v.interpolatedValue = interStage[vNdx];
+ _ = uni;
+ return v;
+ }
+
+ struct FragmentIn {
+ @builtin(position) position: vec4f,
+ @location(0) @interpolate(${interpolate}) interpolatedValue: vec4f,
+ ${fragInCode}
+ };
+
+ struct FragOut {
+ @location(0) out0: vec4f,
+ @location(1) out1: vec4f,
+ @location(2) out2: vec4f,
+ @location(3) out3: vec4f,
+ };
+
+ fn u32ToRGBAUnorm(u: u32) -> vec4f {
+ return vec4f(
+ f32((u >> 24) & 0xFF) / 255.0,
+ f32((u >> 16) & 0xFF) / 255.0,
+ f32((u >> 8) & 0xFF) / 255.0,
+ f32((u >> 0) & 0xFF) / 255.0,
+ );
+ }
+
+ @fragment fn fs(fin: FragmentIn) -> FragOut {
+ var f: FragOut;
+ let v = ${outputCode};
+ let u = bitcast<vec4u>(v);
+ f.out0 = u32ToRGBAUnorm(u[0]);
+ f.out1 = u32ToRGBAUnorm(u[1]);
+ f.out2 = u32ToRGBAUnorm(u[2]);
+ f.out3 = u32ToRGBAUnorm(u[3]);
+ _ = fin.interpolatedValue;
+ return f;
+ }
+ `,
+ });
+
+ const textures = range(4, () => {
+ const texture = t.device.createTexture({
+ size: [width, height],
+ usage:
+ GPUTextureUsage.RENDER_ATTACHMENT |
+ GPUTextureUsage.TEXTURE_BINDING |
+ GPUTextureUsage.COPY_SRC,
+ format: 'rgba8unorm',
+ sampleCount,
+ });
+ t.trackForCleanup(texture);
+ return texture;
+ });
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module,
+ entryPoint: 'vs',
+ },
+ fragment: {
+ module,
+ entryPoint: 'fs',
+ targets: textures.map(() => ({ format: 'rgba8unorm' })),
+ },
+ ...(frontFace && {
+ primitive: {
+ frontFace,
+ },
+ }),
+ multisample: {
+ count: sampleCount,
+ },
+ });
+
+ const uniformBuffer = t.device.createBuffer({
+ size: 8,
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
+ });
+ t.trackForCleanup(uniformBuffer);
+ t.device.queue.writeBuffer(uniformBuffer, 0, new Float32Array([width, height]));
+
+ const viewport = [0, 0, width, height, ...nearFar] as const;
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 0, resource: { buffer: uniformBuffer } }],
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: textures.map(texture => ({
+ view: texture.createView(),
+ loadOp: 'clear',
+ storeOp: 'store',
+ })),
+ });
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.setViewport(viewport[0], viewport[1], viewport[2], viewport[3], viewport[4], viewport[5]);
+ pass.draw(clipSpacePoints.length);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ const buffer = copyRGBA8EncodedFloatTexturesToBufferIncludingMultisampledTextures(t, textures);
+ await buffer.mapAsync(GPUMapMode.READ);
+ return new Float32Array(buffer.getMappedRange());
+}
+
+function checkSampleRectsApproximatelyEqual({
+ width,
+ height,
+ sampleCount,
+ actual,
+ expected,
+ maxDiffULPsForFloatFormat,
+}: {
+ width: number;
+ height: number;
+ sampleCount: number;
+ actual: Float32Array;
+ expected: Float32Array;
+ maxDiffULPsForFloatFormat: number;
+}) {
+ const subrectOrigin = [0, 0, 0];
+ const subrectSize = [width * sampleCount, height, 1];
+ const areaDesc = {
+ bytesPerRow: width * sampleCount * 4 * 4,
+ rowsPerImage: height,
+ subrectOrigin,
+ subrectSize,
+ };
+
+ const format = 'rgba32float';
+ const actTexelView = TexelView.fromTextureDataByReference(
+ format,
+ new Uint8Array(actual.buffer),
+ areaDesc
+ );
+ const expTexelView = TexelView.fromTextureDataByReference(
+ format,
+ new Uint8Array(expected.buffer),
+ areaDesc
+ );
+
+ const failedPixelsMessage = findFailedPixels(
+ format,
+ { x: 0, y: 0, z: 0 },
+ { width: width * sampleCount, height, depthOrArrayLayers: 1 },
+ { actTexelView, expTexelView },
+ { maxDiffULPsForFloatFormat }
+ );
+
+ if (failedPixelsMessage !== undefined) {
+ const msg = 'Texture level had unexpected contents:\n' + failedPixelsMessage;
+ return new ErrorWithExtra(msg, () => ({
+ expTexelView,
+ actTexelView,
+ }));
+ }
+
+ return undefined;
+}
+
+g.test('inputs,position')
+ .desc(
+ `
+ Test fragment shader builtin(position) values.
+
+ Note: @builtin(position) is always a fragment position, never a sample position.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('nearFar', [[0, 1] as const, [0.25, 0.75] as const] as const)
+ .combine('sampleCount', [1, 4] as const)
+ .combine('interpolation', [
+ { type: 'perspective', sampling: 'center' },
+ { type: 'perspective', sampling: 'centroid' },
+ { type: 'perspective', sampling: 'sample' },
+ { type: 'linear', sampling: 'center' },
+ { type: 'linear', sampling: 'centroid' },
+ { type: 'linear', sampling: 'sample' },
+ { type: 'flat' },
+ ] as const)
+ )
+ .beforeAllSubcases(t => {
+ const {
+ interpolation: { type, sampling },
+ } = t.params;
+ t.skipIfInterpolationTypeOrSamplingNotSupported({ type, sampling });
+ })
+ .fn(async t => {
+ const {
+ nearFar,
+ sampleCount,
+ interpolation: { type, sampling },
+ } = t.params;
+ // prettier-ignore
+ const clipSpacePoints = [ // ndc values
+ [0.333, 0.333, 0.333, 0.333], // 1, 1, 1
+ [ 1.0, -3.0, 0.25, 1.0 ], // 1, -3, 0.25
+ [-1.5, 0.5, 0.25, 0.5 ], // -3, 1, 0.5
+ ];
+
+ const interStagePoints = [
+ [1, 2, 3, 4],
+ [5, 6, 7, 8],
+ [9, 10, 11, 12],
+ ];
+
+ const width = 4;
+ const height = 4;
+ const actual = await renderFragmentShaderInputsTo4TexturesAndReadbackValues(t, {
+ interpolationType: type,
+ interpolationSampling: sampling,
+ sampleCount,
+ width,
+ height,
+ nearFar,
+ clipSpacePoints,
+ interStagePoints,
+ fragInCode: '',
+ outputCode: 'fin.position',
+ });
+
+ const expected = generateFragmentInputs({
+ width,
+ height,
+ nearFar,
+ sampleCount,
+ clipSpacePoints,
+ interpolateFn: computeFragmentPosition,
+ });
+
+ // Since @builtin(position) is always a fragment position, never a sample position, check
+ // the first coordinate. It should be 0.5, 0.5 always. This is just to double check
+ // that computeFragmentPosition is generating the correct values.
+ assert(expected[0] === 0.5);
+ assert(expected[1] === 0.5);
+
+ t.expectOK(
+ checkSampleRectsApproximatelyEqual({
+ width,
+ height,
+ sampleCount,
+ actual,
+ expected,
+ maxDiffULPsForFloatFormat: 4,
+ })
+ );
+ });
+
+g.test('inputs,interStage')
+ .desc(
+ `
+ Test fragment shader inter-stage variable values except for centroid interpolation.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('nearFar', [[0, 1] as const, [0.25, 0.75] as const] as const)
+ .combine('sampleCount', [1, 4] as const)
+ .combine('interpolation', [
+ { type: 'perspective', sampling: 'center' },
+ { type: 'perspective', sampling: 'sample' },
+ { type: 'linear', sampling: 'center' },
+ { type: 'linear', sampling: 'sample' },
+ { type: 'flat' },
+ ] as const)
+ )
+ .beforeAllSubcases(t => {
+ const {
+ interpolation: { type, sampling },
+ } = t.params;
+ t.skipIfInterpolationTypeOrSamplingNotSupported({ type, sampling });
+ })
+ .fn(async t => {
+ const {
+ nearFar,
+ sampleCount,
+ interpolation: { type, sampling },
+ } = t.params;
+ // prettier-ignore
+ const clipSpacePoints = [ // ndc values
+ [0.333, 0.333, 0.333, 0.333], // 1, 1, 1
+ [ 1.0, -3.0, 0.25, 1.0 ], // 1, -3, 0.25
+ [-1.5, 0.5, 0.25, 0.5 ], // -3, 1, 0.5
+ ];
+
+ const interStagePoints = [
+ [1, 2, 3, 4],
+ [5, 6, 7, 8],
+ [9, 10, 11, 12],
+ ];
+
+ const width = 4;
+ const height = 4;
+ const actual = await renderFragmentShaderInputsTo4TexturesAndReadbackValues(t, {
+ interpolationType: type,
+ interpolationSampling: sampling,
+ sampleCount,
+ width,
+ height,
+ nearFar,
+ clipSpacePoints,
+ interStagePoints,
+ fragInCode: '',
+ outputCode: 'fin.interpolatedValue',
+ });
+
+ const expected = generateFragmentInputs({
+ width,
+ height,
+ nearFar,
+ sampleCount,
+ clipSpacePoints,
+ interpolateFn: createInterStageInterpolationFn(interStagePoints, type, sampling),
+ });
+
+ t.expectOK(
+ checkSampleRectsApproximatelyEqual({
+ width,
+ height,
+ sampleCount,
+ actual,
+ expected,
+ maxDiffULPsForFloatFormat: 4,
+ })
+ );
+ });
+
+g.test('inputs,interStage,centroid')
+ .desc(
+ `
+ Test fragment shader inter-stage variable values in centroid sampling mode.
+
+ Centroid sampling mode is trying to solve the following issue
+
+ +-------------+
+ |....s1|/ |
+ |......| |
+ |...../| s2 |
+ +------C------+
+ |s3./ | |
+ |../ | |
+ |./ |s4 |
+ +-------------+
+
+ Above is a diagram of a texel where s1, s2, s3, s4 are sample points,
+ C is the center of the texel and the diagonal line is some edge of
+ a triangle. s1 and s3 are inside the triangle. In sampling = 'center'
+ modes, the interpolated value will be relative to C. The problem is,
+ C is outside of the triangle. In sample = 'centroid' mode, the
+ interpolated value will be computed relative to some point inside the
+ portion of the triangle inside the texel. While ideally it would be
+ the actual centroid, the specs from the various APIs suggest the only
+ guarantee is it's inside the triangle.
+
+ So, we set the interStage values to barycentric coords. We expect
+ that when sampling mode is 'center', some interpolated values
+ will be outside of the triangle (ie, one or more of their values will
+ be outside the 0 to 1 range). In sampling mode = 'centroid' mode, none
+ of the values will be outside of the 0 to 1 range.
+
+ Note: generateFragmentInputs below generates "expected". Values not
+ rendered to will be 0. Values rendered to outside the triangle will
+ be -1. Values rendered to inside the triangle will be 1. Manually
+ checking, "expected" for sampling = 'center' should have a couple of
+ -1 values where as "expected" for sampling = 'centroid' should not.
+ This was verified with manual testing.
+
+ Since we only care about inside vs outside of the triangle, having
+ createInterStageInterpolationFn use the interpolated value relative
+ to the sample point when sampling = 'centroid' will give us a value
+ inside the triangle, which is good enough for our test.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('nearFar', [[0, 1] as const, [0.25, 0.75] as const] as const)
+ .combine('sampleCount', [1, 4] as const)
+ .combine('interpolation', [
+ { type: 'perspective', sampling: 'center' },
+ { type: 'perspective', sampling: 'centroid' },
+ { type: 'linear', sampling: 'center' },
+ { type: 'linear', sampling: 'centroid' },
+ ] as const)
+ )
+ .beforeAllSubcases(t => {
+ const {
+ interpolation: { type, sampling },
+ } = t.params;
+ t.skipIfInterpolationTypeOrSamplingNotSupported({ type, sampling });
+ })
+ .fn(async t => {
+ const {
+ nearFar,
+ sampleCount,
+ interpolation: { type, sampling },
+ } = t.params;
+ //
+ // We're drawing 1 triangle that cut the viewport
+ //
+ // -1 0 1
+ // +===+===+ 2
+ // |\..|...|
+ // +---+---+ 1 <---
+ // | \|...| |
+ // +---+---+ 0 | viewport
+ // | |\..| |
+ // +---+---+ -1 <---
+ // | | \|
+ // +===+===+ -2
+
+ // prettier-ignore
+ const clipSpacePoints = [ // ndc values
+ [ 1, -2, 0, 1],
+ [-1, 2, 0, 1],
+ [ 1, 2, 0, 1],
+ ];
+
+ // prettier-ignore
+ const interStagePoints = [
+ [ 1, 0, 0, 0],
+ [ 0, 1, 0, 0],
+ [ 0, 0, 1, 0],
+ ];
+
+ const width = 4;
+ const height = 4;
+ const actual = await renderFragmentShaderInputsTo4TexturesAndReadbackValues(t, {
+ interpolationType: type,
+ interpolationSampling: sampling,
+ sampleCount,
+ width,
+ height,
+ nearFar,
+ clipSpacePoints,
+ interStagePoints,
+ fragInCode: '',
+ outputCode:
+ 'vec4f(select(-1.0, 1.0, all(fin.interpolatedValue >= vec4f(0)) && all(fin.interpolatedValue <= vec4f(1))), 0, 0, 0)',
+ });
+
+ const expected = generateFragmentInputs({
+ width,
+ height,
+ nearFar,
+ sampleCount,
+ clipSpacePoints,
+ interpolateFn: createInterStageInterpolationBetween0And1TestFn(
+ interStagePoints,
+ type,
+ sampling
+ ),
+ });
+
+ t.expectOK(
+ checkSampleRectsApproximatelyEqual({
+ width,
+ height,
+ sampleCount,
+ actual,
+ expected,
+ maxDiffULPsForFloatFormat: 3,
+ })
+ );
+ });
+
+g.test('inputs,sample_index')
+ .desc(
+ `
+ Test fragment shader builtin(sample_index) values.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('nearFar', [[0, 1] as const, [0.25, 0.75] as const] as const)
+ .combine('sampleCount', [1, 4] as const)
+ .combine('interpolation', [
+ { type: 'perspective', sampling: 'center' },
+ { type: 'perspective', sampling: 'centroid' },
+ { type: 'perspective', sampling: 'sample' },
+ { type: 'linear', sampling: 'center' },
+ { type: 'linear', sampling: 'centroid' },
+ { type: 'linear', sampling: 'sample' },
+ { type: 'flat' },
+ ] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.skipIf(t.isCompatibility, 'sample_index is not supported in compatibility mode');
+ })
+ .fn(async t => {
+ const {
+ nearFar,
+ sampleCount,
+ interpolation: { type, sampling },
+ } = t.params;
+ // prettier-ignore
+ const clipSpacePoints = [ // ndc values
+ [0.333, 0.333, 0.333, 0.333], // 1, 1, 1
+ [ 1.0, -3.0, 0.25, 1.0 ], // 1, -3, 0.25
+ [-1.5, 0.5, 0.25, 0.5 ], // -3, 1, 0.5
+ ];
+
+ const interStagePoints = [
+ [1, 2, 3, 4],
+ [5, 6, 7, 8],
+ [9, 10, 11, 12],
+ ];
+
+ const width = 4;
+ const height = 4;
+ const actual = await renderFragmentShaderInputsTo4TexturesAndReadbackValues(t, {
+ interpolationType: type,
+ interpolationSampling: sampling,
+ sampleCount,
+ width,
+ height,
+ nearFar,
+ clipSpacePoints,
+ interStagePoints,
+ fragInCode: `@builtin(sample_index) sampleIndex: u32,`,
+ outputCode: 'vec4f(f32(fin.sampleIndex), 0, 0, 0)',
+ });
+
+ const expected = generateFragmentInputs({
+ width,
+ height,
+ nearFar,
+ sampleCount,
+ clipSpacePoints,
+ interpolateFn: computeFragmentSampleIndex,
+ });
+
+ t.expectOK(
+ checkSampleRectsApproximatelyEqual({
+ width,
+ height,
+ sampleCount,
+ actual,
+ expected,
+ maxDiffULPsForFloatFormat: 1,
+ })
+ );
+ });
+
+g.test('inputs,front_facing')
+ .desc(
+ `
+ Test fragment shader builtin(front_facing) values.
+
+ Draws a quad from 2 triangles that entirely cover clip space. (see diagram below in code)
+ One triangle is clockwise, the other is counter clockwise. The triangles
+ bisect pixels so that different samples are covered by each triangle so that some
+ samples should get different values for front_facing for the same fragment.
+ `
+ )
+ .params(u =>
+ u //
+ .combine('nearFar', [[0, 1] as const, [0.25, 0.75] as const] as const)
+ .combine('sampleCount', [1, 4] as const)
+ .combine('frontFace', ['cw', 'ccw'] as const)
+ .combine('interpolation', [
+ { type: 'perspective', sampling: 'center' },
+ { type: 'perspective', sampling: 'centroid' },
+ { type: 'perspective', sampling: 'sample' },
+ { type: 'linear', sampling: 'center' },
+ { type: 'linear', sampling: 'centroid' },
+ { type: 'linear', sampling: 'sample' },
+ { type: 'flat' },
+ ] as const)
+ )
+ .beforeAllSubcases(t => {
+ const {
+ interpolation: { type, sampling },
+ } = t.params;
+ t.skipIfInterpolationTypeOrSamplingNotSupported({ type, sampling });
+ })
+ .fn(async t => {
+ const {
+ nearFar,
+ sampleCount,
+ frontFace,
+ interpolation: { type, sampling },
+ } = t.params;
+ //
+ // We're drawing 2 triangles starting at y = -2 to y = +2
+ //
+ // -1 0 1
+ // +===+===+ 2
+ // |\ | |
+ // +---+---+ 1 <---
+ // | \| | |
+ // +---+---+ 0 | viewport
+ // | |\ | |
+ // +---+---+ -1 <---
+ // | | \|
+ // +===+===+ -2
+
+ // prettier-ignore
+ const clipSpacePoints = [
+ // ccw
+ [-1, -2, 0, 1],
+ [ 1, -2, 0, 1],
+ [-1, 2, 0, 1],
+
+ // cw
+ [ 1, -2, 0, 1],
+ [-1, 2, 0, 1],
+ [ 1, 2, 0, 1],
+ ];
+
+ const interStagePoints = [
+ [1, 2, 3, 4],
+ [5, 6, 7, 8],
+ [9, 10, 11, 12],
+
+ [13, 14, 15, 16],
+ [17, 18, 19, 20],
+ [21, 22, 23, 24],
+ ];
+
+ const width = 4;
+ const height = 4;
+ const actual = await renderFragmentShaderInputsTo4TexturesAndReadbackValues(t, {
+ interpolationType: type,
+ interpolationSampling: sampling,
+ frontFace,
+ sampleCount,
+ width,
+ height,
+ nearFar,
+ clipSpacePoints,
+ interStagePoints,
+ fragInCode: '@builtin(front_facing) frontFacing: bool,',
+ outputCode: 'vec4f(select(0.0, 1.0, fin.frontFacing), 0, 0, 0)',
+ });
+
+ const expected = generateFragmentInputs({
+ width,
+ height,
+ nearFar,
+ sampleCount,
+ clipSpacePoints,
+ frontFace,
+ interpolateFn: computeFragmentFrontFacing,
+ });
+
+ assert(expected.indexOf(0) >= 0, 'expect some values to be 0');
+ assert(expected.findIndex(v => v !== 0) >= 0, 'expect some values to be non 0');
+
+ t.expectOK(
+ checkSampleRectsApproximatelyEqual({
+ width,
+ height,
+ sampleCount,
+ actual,
+ expected,
+ maxDiffULPsForFloatFormat: 0,
+ })
+ );
+ });
+
+g.test('inputs,sample_mask')
+ .desc(
+ `
+ Test fragment shader builtin(sample_mask) values.
+
+ Draws various triangles that should trigger different sample_mask values.
+ Checks that sample_mask matches what's expected. Note: the triangles
+ are selected so they do not intersect sample points as we don't want
+ to test precision issues on whether or not a sample point is inside
+ or outside the triangle when right on the edge.
+
+ Example: x=-1, y=2, it draws the following triangle
+
+ [ -0.8, -2 ]
+ [ 1.2, 2 ]
+ [ -0.8, 2 ]
+
+ On to a 4x4 pixel texture
+
+ -0.8, 2
+ .----------------------. 1.2 2
+ |...................../
+ |..................../
+ |.................../
+ |................../
+ |................./
+ +-|---+-----+-----+/----+ ---
+ | |...|.....|...../ | ^
+ | |...|.....|..../| | |
+ +-|---+-----+---/-+-----+ |
+ | |...|.....|../ | | |
+ | |...|.....|./ | | |
+ +-|---+-----+/----+-----+ texture / clip space
+ | |...|...../ | | |
+ | |...|..../| | | |
+ +-|---+---/-+-----+-----+ |
+ | |...|../ | | | |
+ | |...|./ | | | V
+ +-|---+/----+-----+-----+ ---
+ |.../
+ |../
+ |./
+ |/
+ /
+ .
+ -0.8, -2
+
+ Inside an individual pixel you might see this situation
+
+ +-------------+
+ |....s1|/ |
+ |......| |
+ |...../| s2 |
+ +------C------+
+ |s3./ | |
+ |../ | |
+ |./ |s4 |
+ +-------------+
+
+ where s1, s2, s3, s4, are sample points and C is the center. For a sampleCount = 4 texture
+ the sample_mask is expected to emit sample_mask = 0b0101
+
+ ref: https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_standard_multisample_quality_levels
+ `
+ )
+ .params(u =>
+ u //
+ .combine('nearFar', [[0, 1] as const, [0.25, 0.75] as const] as const)
+ .combine('sampleCount', [1, 4] as const)
+ .combine('interpolation', [
+ // given that 'sample' effects whether things are run per-sample or per-fragment
+ // we test all of these to make sure they don't affect the result differently than expected.
+ { type: 'perspective', sampling: 'center' },
+ { type: 'perspective', sampling: 'centroid' },
+ { type: 'perspective', sampling: 'sample' },
+ { type: 'linear', sampling: 'center' },
+ { type: 'linear', sampling: 'centroid' },
+ { type: 'linear', sampling: 'sample' },
+ { type: 'flat' },
+ ] as const)
+ .beginSubcases()
+ .combineWithParams([
+ { x: -1, y: -1 },
+ { x: -1, y: -2 },
+ { x: -1, y: 1 },
+ { x: -1, y: 3 },
+ { x: -2, y: -1 },
+ { x: -2, y: 3 },
+ { x: -3, y: -1 },
+ { x: -3, y: -2 },
+ { x: -3, y: 1 },
+ { x: 1, y: -1 },
+ { x: 1, y: -3 },
+ { x: 1, y: 1 },
+ { x: 1, y: 2 },
+ { x: 2, y: -2 },
+ { x: 2, y: -3 },
+ { x: 2, y: 1 },
+ { x: 2, y: 2 },
+ { x: 3, y: -1 },
+ { x: 3, y: -3 },
+ { x: 3, y: 1 },
+ { x: 3, y: 2 },
+ { x: 3, y: 3 },
+ ])
+ )
+ .beforeAllSubcases(t => {
+ const {
+ interpolation: { type, sampling },
+ } = t.params;
+ t.skipIfInterpolationTypeOrSamplingNotSupported({ type, sampling });
+ })
+ .fn(async t => {
+ const {
+ x,
+ y,
+ nearFar,
+ sampleCount,
+ interpolation: { type, sampling },
+ } = t.params;
+ // prettier-ignore
+ const clipSpacePoints = [
+ [ x + 0.2, -y, 0, 1],
+ [-x + 0.2, y, 0, 1],
+ [ x + 0.2, y, 0, 1],
+ ];
+
+ const interStagePoints = [
+ [13, 14, 15, 16],
+ [17, 18, 19, 20],
+ [21, 22, 23, 24],
+ ];
+
+ const width = 4;
+ const height = 4;
+ const actual = await renderFragmentShaderInputsTo4TexturesAndReadbackValues(t, {
+ interpolationType: type,
+ interpolationSampling: sampling,
+ sampleCount,
+ width,
+ height,
+ nearFar,
+ clipSpacePoints,
+ interStagePoints,
+ fragInCode: '@builtin(sample_mask) sample_mask: u32,',
+ outputCode: 'vec4f(f32(fin.sample_mask), 0, 0, 0)',
+ });
+
+ const expected = generateFragmentInputs({
+ width,
+ height,
+ nearFar,
+ sampleCount,
+ clipSpacePoints,
+ interpolateFn: computeSampleMask,
+ });
+
+ t.expectOK(
+ checkSampleRectsApproximatelyEqual({
+ width,
+ height,
+ sampleCount,
+ actual,
+ expected,
+ maxDiffULPsForFloatFormat: 0,
+ })
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/user_io.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/user_io.spec.ts
new file mode 100644
index 0000000000..0c26f89872
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/user_io.spec.ts
@@ -0,0 +1,213 @@
+export const description = `
+Test for user-defined shader I/O.
+
+passthrough:
+ * Data passed into vertex shader as uints and converted to test type
+ * Passed from vertex to fragment as test type
+ * Output from fragment shader as uint
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { range } from '../../../../common/util/util.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+function generateInterstagePassthroughCode(type: string): string {
+ return `
+${type === 'f16' ? 'enable f16;' : ''}
+struct IOData {
+ @builtin(position) pos : vec4f,
+ @location(0) @interpolate(flat) user0 : ${type},
+ @location(1) @interpolate(flat) user1 : vec2<${type}>,
+ @location(2) @interpolate(flat) user2 : vec4<${type}>,
+}
+
+struct VertexInput {
+ @builtin(vertex_index) idx : u32,
+ @location(0) in0 : u32,
+ @location(1) in1 : vec2u,
+ @location(2) in2 : vec4u,
+}
+
+@vertex
+fn vsMain(input : VertexInput) -> IOData {
+ const vertices = array(
+ vec4f(-1, -1, 0, 1),
+ vec4f(-1, 1, 0, 1),
+ vec4f( 1, -1, 0, 1),
+ );
+ var data : IOData;
+ data.pos = vertices[input.idx];
+ data.user0 = ${type}(input.in0);
+ data.user1 = vec2<${type}>(input.in1);
+ data.user2 = vec4<${type}>(input.in2);
+ return data;
+}
+
+struct FragOutput {
+ @location(0) out0 : u32,
+ @location(1) out1 : vec2u,
+ @location(2) out2 : vec4u,
+}
+
+@fragment
+fn fsMain(input : IOData) -> FragOutput {
+ var out : FragOutput;
+ out.out0 = u32(input.user0);
+ out.out1 = vec2u(input.user1);
+ out.out2 = vec4u(input.user2);
+ return out;
+}
+`;
+}
+
+function drawPassthrough(t: GPUTest, code: string) {
+ // Default limit is 32 bytes of color attachments.
+ // These attachments use 28 bytes (which is why vec3 is skipped).
+ const formats: GPUTextureFormat[] = ['r32uint', 'rg32uint', 'rgba32uint'];
+ const components = [1, 2, 4];
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({ code }),
+ entryPoint: 'vsMain',
+ buffers: [
+ {
+ arrayStride: 4,
+ attributes: [
+ {
+ format: 'uint32',
+ offset: 0,
+ shaderLocation: 0,
+ },
+ ],
+ },
+ {
+ arrayStride: 8,
+ attributes: [
+ {
+ format: 'uint32x2',
+ offset: 0,
+ shaderLocation: 1,
+ },
+ ],
+ },
+ {
+ arrayStride: 16,
+ attributes: [
+ {
+ format: 'uint32x4',
+ offset: 0,
+ shaderLocation: 2,
+ },
+ ],
+ },
+ ],
+ },
+ fragment: {
+ module: t.device.createShaderModule({ code }),
+ entryPoint: 'fsMain',
+ targets: formats.map(x => {
+ return { format: x };
+ }),
+ },
+ primitive: {
+ topology: 'triangle-list',
+ },
+ });
+
+ const vertexBuffer = t.makeBufferWithContents(
+ new Uint32Array([
+ // scalar: offset 0
+ 1, 1, 1, 0,
+ // vec2: offset 16
+ 2, 2, 2, 2, 2, 2, 0, 0,
+ // vec4: offset 48
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ ]),
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.VERTEX
+ );
+
+ const bytesPerComponent = 4;
+ // 256 is the minimum bytes per row for texture to buffer copies.
+ const width = 256 / bytesPerComponent;
+ const height = 2;
+ const copyWidth = 4;
+ const outputTextures = range(3, i => {
+ const texture = t.device.createTexture({
+ size: [width, height],
+ usage:
+ GPUTextureUsage.COPY_SRC |
+ GPUTextureUsage.RENDER_ATTACHMENT |
+ GPUTextureUsage.TEXTURE_BINDING,
+ format: formats[i],
+ });
+ t.trackForCleanup(texture);
+ return texture;
+ });
+
+ let bufferSize = 1;
+ for (const comp of components) {
+ bufferSize *= comp;
+ }
+ bufferSize *= outputTextures.length * bytesPerComponent * copyWidth;
+ const outputBuffer = t.device.createBuffer({
+ size: bufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+ });
+ t.trackForCleanup(outputBuffer);
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: outputTextures.map(t => ({
+ view: t.createView(),
+ loadOp: 'clear',
+ storeOp: 'store',
+ })),
+ });
+ pass.setPipeline(pipeline);
+ pass.setVertexBuffer(0, vertexBuffer, 0, 12);
+ pass.setVertexBuffer(1, vertexBuffer, 16, 24);
+ pass.setVertexBuffer(2, vertexBuffer, 48, 48);
+ pass.draw(3);
+ pass.end();
+
+ // Copy 'copyWidth' samples from each attachment into a buffer to check the results.
+ let offset = 0;
+ let expectArray: number[] = [];
+ for (let i = 0; i < outputTextures.length; i++) {
+ encoder.copyTextureToBuffer(
+ { texture: outputTextures[i] },
+ {
+ buffer: outputBuffer,
+ offset,
+ bytesPerRow: bytesPerComponent * components[i] * width,
+ rowsPerImage: height,
+ },
+ { width: copyWidth, height: 1 }
+ );
+ offset += components[i] * bytesPerComponent * copyWidth;
+ for (let j = 0; j < components[i]; j++) {
+ const value = i + 1;
+ expectArray = expectArray.concat([value, value, value, value]);
+ }
+ }
+ t.queue.submit([encoder.finish()]);
+
+ const expect = new Uint32Array(expectArray);
+ t.expectGPUBufferValuesEqual(outputBuffer, expect);
+}
+
+g.test('passthrough')
+ .desc('Tests passing user-defined data from vertex input through fragment output')
+ .params(u => u.combine('type', ['f32', 'f16', 'i32', 'u32'] as const))
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const code = generateInterstagePassthroughCode(t.params.type);
+ drawPassthrough(t, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/workgroup_size.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/workgroup_size.spec.ts
new file mode 100644
index 0000000000..278265b7ad
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/shader_io/workgroup_size.spec.ts
@@ -0,0 +1,150 @@
+export const description = `Test that workgroup size is set correctly`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { iterRange } from '../../../../common/util/util.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+function checkResults(
+ sizeX: number,
+ sizeY: number,
+ sizeZ: number,
+ numWGs: number,
+ data: Uint32Array
+): Error | undefined {
+ const totalInvocations = sizeX * sizeY * sizeZ;
+ for (let i = 0; i < numWGs; i++) {
+ const wgx_data = data[4 * i + 0];
+ const wgy_data = data[4 * i + 1];
+ const wgz_data = data[4 * i + 2];
+ const total_data = data[4 * i + 3];
+ if (wgx_data !== sizeX) {
+ let msg = `Incorrect workgroup size x dimension for wg ${i}:\n`;
+ msg += `- expected: ${wgx_data}\n`;
+ msg += `- got: ${sizeX}`;
+ return Error(msg);
+ }
+ if (wgy_data !== sizeY) {
+ let msg = `Incorrect workgroup size y dimension for wg ${i}:\n`;
+ msg += `- expected: ${wgy_data}\n`;
+ msg += `- got: ${sizeY}`;
+ return Error(msg);
+ }
+ if (wgz_data !== sizeZ) {
+ let msg = `Incorrect workgroup size y dimension for wg ${i}:\n`;
+ msg += `- expected: ${wgz_data}\n`;
+ msg += `- got: ${sizeZ}`;
+ return Error(msg);
+ }
+ if (total_data !== totalInvocations) {
+ let msg = `Incorrect workgroup total invocations for wg ${i}:\n`;
+ msg += `- expected: ${total_data}\n`;
+ msg += `- got: ${totalInvocations}`;
+ return Error(msg);
+ }
+ }
+ return undefined;
+}
+
+g.test('workgroup_size')
+ .desc(`Test workgroup size is set correctly`)
+ .params(u =>
+ u
+ .combine('wgx', [1, 3, 4, 8, 11, 16, 51, 64, 128, 256] as const)
+ .combine('wgy', [1, 3, 4, 8, 16, 51, 64, 256] as const)
+ .combine('wgz', [1, 3, 11, 16, 128, 256] as const)
+ .beginSubcases()
+ )
+ .fn(async t => {
+ const {
+ maxComputeWorkgroupSizeX,
+ maxComputeWorkgroupSizeY,
+ maxComputeWorkgroupSizeZ,
+ maxComputeInvocationsPerWorkgroup,
+ } = t.device.limits;
+ t.skipIf(
+ t.params.wgx > maxComputeWorkgroupSizeX,
+ `workgroup size x: ${t.params.wgx} > limit: ${maxComputeWorkgroupSizeX}`
+ );
+ t.skipIf(
+ t.params.wgy > maxComputeWorkgroupSizeY,
+ `workgroup size x: ${t.params.wgy} > limit: ${maxComputeWorkgroupSizeY}`
+ );
+ t.skipIf(
+ t.params.wgz > maxComputeWorkgroupSizeZ,
+ `workgroup size x: ${t.params.wgz} > limit: ${maxComputeWorkgroupSizeZ}`
+ );
+ const totalInvocations = t.params.wgx * t.params.wgy * t.params.wgz;
+ t.skipIf(
+ totalInvocations > maxComputeInvocationsPerWorkgroup,
+ `workgroup size: ${totalInvocations} > limit: ${maxComputeInvocationsPerWorkgroup}`
+ );
+
+ const code = `
+struct Values {
+ x : atomic<u32>,
+ y : atomic<u32>,
+ z : atomic<u32>,
+ total : atomic<u32>,
+}
+
+@group(0) @binding(0)
+var<storage, read_write> v : array<Values>;
+
+@compute @workgroup_size(${t.params.wgx}, ${t.params.wgy}, ${t.params.wgz})
+fn main(@builtin(local_invocation_id) lid : vec3u,
+ @builtin(workgroup_id) wgid : vec3u) {
+ atomicMax(&v[wgid.x].x, lid.x + 1);
+ atomicMax(&v[wgid.x].y, lid.y + 1);
+ atomicMax(&v[wgid.x].z, lid.z + 1);
+ atomicAdd(&v[wgid.x].total, 1);
+}`;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code,
+ }),
+ entryPoint: 'main',
+ },
+ });
+
+ const numWorkgroups = totalInvocations < 256 ? 5 : 3;
+ const buffer = t.makeBufferWithContents(
+ new Uint32Array([...iterRange(numWorkgroups * 4, _i => 0)]),
+ GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ );
+ t.trackForCleanup(buffer);
+
+ const bg = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer,
+ },
+ },
+ ],
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bg);
+ pass.dispatchWorkgroups(numWorkgroups, 1, 1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ const bufferReadback = await t.readGPUBufferRangeTyped(buffer, {
+ srcByteOffset: 0,
+ type: Uint32Array,
+ typedLength: 4 * numWorkgroups,
+ method: 'copy',
+ });
+ const data: Uint32Array = bufferReadback.data;
+
+ t.expectOK(checkResults(t.params.wgx, t.params.wgy, t.params.wgz, numWorkgroups, data));
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/stage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/stage.spec.ts
new file mode 100644
index 0000000000..6e06e67e37
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/stage.spec.ts
@@ -0,0 +1,133 @@
+export const description = `Test trivial shaders for each shader stage kind`;
+
+// There are many many more shaders executed in other tests.
+
+import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { GPUTest } from '../../gpu_test.js';
+import { checkElementsEqual } from '../../util/check_contents.js';
+
+export const g = makeTestGroup(GPUTest);
+
+g.test('basic_compute')
+ .desc(`Test a trivial compute shader`)
+ .fn(async t => {
+ const code = `
+
+@group(0) @binding(0)
+var<storage, read_write> v : vec4u;
+
+@compute @workgroup_size(1)
+fn main() {
+ v = vec4u(1,2,3,42);
+}`;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code,
+ }),
+ entryPoint: 'main',
+ },
+ });
+
+ const buffer = t.makeBufferWithContents(
+ new Uint32Array([0, 0, 0, 0]),
+ GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ );
+ t.trackForCleanup(buffer);
+
+ const bg = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer,
+ },
+ },
+ ],
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bg);
+ pass.dispatchWorkgroups(1, 1, 1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ const bufferReadback = await t.readGPUBufferRangeTyped(buffer, {
+ srcByteOffset: 0,
+ type: Uint32Array,
+ typedLength: 4,
+ method: 'copy',
+ });
+ const got: Uint32Array = bufferReadback.data;
+ const expected = new Uint32Array([1, 2, 3, 42]);
+
+ t.expectOK(checkElementsEqual(got, expected));
+ });
+
+g.test('basic_render')
+ .desc(`Test trivial vertex and fragment shaders`)
+ .fn(t => {
+ const code = `
+@vertex
+fn vert_main(@builtin(vertex_index) idx: u32) -> @builtin(position) vec4f {
+ // A right triangle covering the whole framebuffer.
+ const pos = array(
+ vec2f(-1,-3),
+ vec2f(3,1),
+ vec2f(-1,1));
+ return vec4f(pos[idx], 0, 1);
+}
+
+@fragment
+fn frag_main() -> @location(0) vec4f {
+ return vec4(0, 1, 0, 1); // green
+}
+`;
+ const module = t.device.createShaderModule({ code });
+
+ const [width, height] = [8, 8] as const;
+ const format = 'rgba8unorm' as const;
+ const texture = t.device.createTexture({
+ size: { width, height },
+ usage:
+ GPUTextureUsage.RENDER_ATTACHMENT |
+ GPUTextureUsage.TEXTURE_BINDING |
+ GPUTextureUsage.COPY_SRC,
+ format,
+ });
+
+ // We'll copy one pixel only.
+ const dst = t.device.createBuffer({
+ size: 4,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+ });
+
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: { module, entryPoint: 'vert_main' },
+ fragment: { module, entryPoint: 'frag_main', targets: [{ format }] },
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [{ view: texture.createView(), loadOp: 'clear', storeOp: 'store' }],
+ });
+ pass.setPipeline(pipeline);
+ pass.draw(3);
+ pass.end();
+
+ encoder.copyTextureToBuffer(
+ { texture, mipLevel: 0, origin: { x: 0, y: 0, z: 0 } },
+ { buffer: dst, bytesPerRow: 256 },
+ { width: 1, height: 1, depthOrArrayLayers: 1 }
+ );
+ t.queue.submit([encoder.finish()]);
+
+ // Expect one green pixel.
+ t.expectGPUBufferValuesEqual(dst, new Uint8Array([0x00, 0xff, 0x00, 0xff]));
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/statement/compound.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/statement/compound.spec.ts
new file mode 100644
index 0000000000..aed0cc2245
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/statement/compound.spec.ts
@@ -0,0 +1,137 @@
+export const description = `
+Compound statement execution.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { TypedArrayBufferView } from '../../../../common/util/util.js';
+import { GPUTest } from '../../../gpu_test.js';
+
+export const g = makeTestGroup(GPUTest);
+
+/**
+ * Builds, runs then checks the output of a statement shader test.
+ *
+ * @param t The test object
+ * @param ty The WGSL scalar type to be written
+ * @param values The expected output values of type ty
+ * @param wgsl_main The body of the WGSL entry point.
+ */
+export function runStatementTest(
+ t: GPUTest,
+ ty: string,
+ values: TypedArrayBufferView,
+ wgsl_main: string
+) {
+ const wgsl = `
+struct Outputs {
+ data : array<${ty}>,
+};
+var<private> count: u32 = 0;
+
+@group(0) @binding(1) var<storage, read_write> outputs : Outputs;
+
+fn put(value : ${ty}) {
+ outputs.data[count] = value;
+ count += 1;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ _ = &outputs;
+ ${wgsl_main}
+}
+`;
+
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({ code: wgsl }),
+ entryPoint: 'main',
+ },
+ });
+
+ const maxOutputValues = 1000;
+ const outputBuffer = t.device.createBuffer({
+ size: 4 * (1 + maxOutputValues),
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+ });
+
+ const bindGroup = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [{ binding: 1, resource: { buffer: outputBuffer } }],
+ });
+
+ // Run the shader.
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bindGroup);
+ pass.dispatchWorkgroups(1);
+ pass.end();
+ t.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesEqual(outputBuffer, values);
+}
+
+// Consider a declaration X of identifier 'x' inside a compound statement.
+// Check the value of 'x' at various places relative to X:
+// a { b; X=c; d; { e; } } f;
+
+const kTests = {
+ uses: {
+ // Observe values without conflicting declarations.
+ src: `let x = 1;
+ put(x);
+ {
+ put(x);
+ let x = x+1; // The declaration in question
+ put(x);
+ {
+ put(x);
+ }
+ put(x);
+ }
+ put(x);`,
+ values: [1, 1, 2, 2, 2, 1],
+ },
+ shadowed: {
+ // Observe values when shadowed
+ src: `let x = 1;
+ put(x);
+ {
+ put(x);
+ let x = x+1; // The declaration in question
+ put(x);
+ {
+ let x = x+1; // A shadow
+ put(x);
+ }
+ put(x);
+ }
+ put(x);`,
+ values: [1, 1, 2, 3, 2, 1],
+ },
+ gone: {
+ // The declaration goes out of scope.
+ src: `{
+ let x = 2; // The declaration in question
+ put(x);
+ }
+ let x = 1;
+ put(x);`,
+ values: [2, 1],
+ },
+} as const;
+
+g.test('decl')
+ .desc('Tests the value of a declared value in a compound statment.')
+ .params(u => u.combine('case', keysOf(kTests)))
+ .fn(t => {
+ runStatementTest(
+ t,
+ 'i32',
+ new Int32Array(kTests[t.params.case].values),
+ kTests[t.params.case].src
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/statement/discard.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/statement/discard.spec.ts
new file mode 100644
index 0000000000..058ff50f17
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/statement/discard.spec.ts
@@ -0,0 +1,645 @@
+export const description = `
+Execution tests for discard.
+
+The discard statement converts invocations into helpers.
+This results in the following conditions:
+ * No outputs are written
+ * No resources are written
+ * Atomics are undefined
+
+Conditions that still occur:
+ * Derivative calculations are correct
+ * Reads
+ * Writes to non-external memory
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { iterRange } from '../../../../common/util/util.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { checkElementsPassPredicate } from '../../../util/check_contents.js';
+
+export const g = makeTestGroup(GPUTest);
+
+// Framebuffer dimensions
+const kWidth = 64;
+const kHeight = 64;
+
+const kSharedCode = `
+@group(0) @binding(0) var<storage, read_write> output: array<vec2f>;
+@group(0) @binding(1) var<storage, read_write> atomicIndex : atomic<u32>;
+@group(0) @binding(2) var<storage> uniformValues : array<u32, 5>;
+
+@vertex
+fn vsMain(@builtin(vertex_index) index : u32) -> @builtin(position) vec4f {
+ const vertices = array(
+ vec2(-1, -1), vec2(-1, 0), vec2( 0, -1),
+ vec2(-1, 0), vec2( 0, 0), vec2( 0, -1),
+
+ vec2( 0, -1), vec2( 0, 0), vec2( 1, -1),
+ vec2( 0, 0), vec2( 1, 0), vec2( 1, -1),
+
+ vec2(-1, 0), vec2(-1, 1), vec2( 0, 0),
+ vec2(-1, 1), vec2( 0, 1), vec2( 0, 0),
+
+ vec2( 0, 0), vec2( 0, 1), vec2( 1, 0),
+ vec2( 0, 1), vec2( 1, 1), vec2( 1, 0),
+ );
+ return vec4f(vec2f(vertices[index]), 0, 1);
+}
+`;
+
+function drawFullScreen(
+ t: GPUTest,
+ code: string,
+ dataChecker: (a: Float32Array) => Error | undefined,
+ framebufferChecker: (a: Uint32Array) => Error | undefined
+) {
+ const pipeline = t.device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: t.device.createShaderModule({ code }),
+ entryPoint: 'vsMain',
+ },
+ fragment: {
+ module: t.device.createShaderModule({ code }),
+ entryPoint: 'fsMain',
+ targets: [{ format: 'r32uint' }],
+ },
+ primitive: {
+ topology: 'triangle-list',
+ },
+ });
+
+ const bytesPerWord = 4;
+ const framebuffer = t.device.createTexture({
+ size: [kWidth, kHeight],
+ usage:
+ GPUTextureUsage.COPY_SRC |
+ GPUTextureUsage.COPY_DST |
+ GPUTextureUsage.RENDER_ATTACHMENT |
+ GPUTextureUsage.TEXTURE_BINDING,
+ format: 'r32uint',
+ });
+ t.trackForCleanup(framebuffer);
+
+ // Create a buffer to copy the framebuffer contents into.
+ // Initialize with a sentinel value and load this buffer to detect unintended writes.
+ const fbBuffer = t.makeBufferWithContents(
+ new Uint32Array([...iterRange(kWidth * kHeight, x => kWidth * kHeight)]),
+ GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
+ );
+
+ // Create a buffer to hold the storage shader resources.
+ // (0,0) = vec2u width * height
+ // (0,1) = u32
+ const dataSize = 2 * kWidth * kHeight * bytesPerWord;
+ const dataBufferSize = dataSize + bytesPerWord;
+ const dataBuffer = t.device.createBuffer({
+ size: dataBufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE,
+ });
+
+ const uniformSize = bytesPerWord * 5;
+ const uniformBuffer = t.makeBufferWithContents(
+ // Loop bound, [derivative constants].
+ new Uint32Array([4, 1, 4, 4, 7]),
+ GPUBufferUsage.COPY_DST | GPUBufferUsage.STORAGE
+ );
+
+ // 'atomicIndex' packed at the end of the buffer.
+ const bg = t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: {
+ buffer: dataBuffer,
+ offset: 0,
+ size: dataSize,
+ },
+ },
+ {
+ binding: 1,
+ resource: {
+ buffer: dataBuffer,
+ offset: dataSize,
+ size: bytesPerWord,
+ },
+ },
+ {
+ binding: 2,
+ resource: {
+ buffer: uniformBuffer,
+ offset: 0,
+ size: uniformSize,
+ },
+ },
+ ],
+ });
+
+ const encoder = t.device.createCommandEncoder();
+ encoder.copyBufferToTexture(
+ {
+ buffer: fbBuffer,
+ offset: 0,
+ bytesPerRow: kWidth * bytesPerWord,
+ rowsPerImage: kHeight,
+ },
+ { texture: framebuffer },
+ { width: kWidth, height: kHeight }
+ );
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: framebuffer.createView(),
+ loadOp: 'load',
+ storeOp: 'store',
+ },
+ ],
+ });
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(0, bg);
+ pass.draw(24);
+ pass.end();
+ encoder.copyTextureToBuffer(
+ { texture: framebuffer },
+ {
+ buffer: fbBuffer,
+ offset: 0,
+ bytesPerRow: kWidth * bytesPerWord,
+ rowsPerImage: kHeight,
+ },
+ { width: kWidth, height: kHeight }
+ );
+ t.queue.submit([encoder.finish()]);
+
+ t.expectGPUBufferValuesPassCheck(dataBuffer, dataChecker, {
+ type: Float32Array,
+ typedLength: dataSize / bytesPerWord,
+ });
+
+ t.expectGPUBufferValuesPassCheck(fbBuffer, framebufferChecker, {
+ type: Uint32Array,
+ typedLength: kWidth * kHeight,
+ });
+}
+
+g.test('all')
+ .desc('Test a shader that discards all fragments')
+ .fn(t => {
+ const code = `
+${kSharedCode}
+
+@fragment
+fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 {
+ _ = uniformValues[0];
+ discard;
+ let idx = atomicAdd(&atomicIndex, 1);
+ output[idx] = pos.xy;
+ return 1;
+}
+`;
+
+ // No storage writes occur.
+ const dataChecker = (a: Float32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ return value === 0;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'data exp ==',
+ getValueForCell: (idx: number) => {
+ return 0;
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ // No fragment outputs occur.
+ const fbChecker = (a: Uint32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ return value === kWidth * kHeight;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'fb exp ==',
+ getValueForCell: (idx: number) => {
+ return 0;
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ drawFullScreen(t, code, dataChecker, fbChecker);
+ });
+
+g.test('three_quarters')
+ .desc('Test a shader that discards all but the upper-left quadrant fragments')
+ .fn(t => {
+ const code = `
+${kSharedCode}
+
+@fragment
+fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 {
+ _ = uniformValues[0];
+ if (pos.x >= 0.5 * ${kWidth} || pos.y >= 0.5 * ${kHeight}) {
+ discard;
+ }
+ let idx = atomicAdd(&atomicIndex, 1);
+ output[idx] = pos.xy;
+ return idx;
+}
+`;
+
+ // Only the the upper left quadrant is kept.
+ const dataChecker = (a: Float32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ const is_x = idx % 2 === 0;
+ if (is_x) {
+ return value < 0.5 * kWidth;
+ } else {
+ return value < 0.5 * kHeight;
+ }
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'data exp ==',
+ getValueForCell: (idx: number): number | string => {
+ const is_x = idx % 2 === 0;
+ if (is_x) {
+ const x = Math.floor(idx / 2) % kWidth;
+ if (x >= kWidth / 2) {
+ return 0;
+ }
+ } else {
+ const y = Math.floor((idx - 1) / kWidth);
+ if (y >= kHeight / 2) {
+ return 0;
+ }
+ }
+ if (is_x) {
+ return `< ${0.5 * kWidth}`;
+ } else {
+ return `< ${0.5 * kHeight}`;
+ }
+ },
+ },
+ ],
+ }
+ );
+ };
+ const fbChecker = (a: Uint32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ const x = idx % kWidth;
+ const y = Math.floor(idx / kWidth);
+ if (x < kWidth / 2 && y < kHeight / 2) {
+ return value < (kWidth * kHeight) / 4;
+ } else {
+ return value === kWidth * kHeight;
+ }
+ return value < (kWidth * kHeight) / 4;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'fb exp ==',
+ getValueForCell: (idx: number) => {
+ const x = idx % kWidth;
+ const y = Math.floor(idx / kWidth);
+ if (x < kWidth / 2 && y < kHeight / 2) {
+ return 'any';
+ } else {
+ return 0;
+ }
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ drawFullScreen(t, code, dataChecker, fbChecker);
+ });
+
+g.test('function_call')
+ .desc('Test discards happening in a function call')
+ .fn(t => {
+ const code = `
+${kSharedCode}
+
+fn foo(pos : vec2f) {
+ let p = vec2i(pos);
+ if p.x <= ${kWidth} / 2 && p.y <= ${kHeight} / 2 {
+ discard;
+ }
+ if p.x >= ${kWidth} / 2 && p.y >= ${kHeight} / 2 {
+ discard;
+ }
+}
+
+@fragment
+fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 {
+ _ = uniformValues[0];
+ foo(pos.xy);
+ let idx = atomicAdd(&atomicIndex, 1);
+ output[idx] = pos.xy;
+ return idx;
+}
+`;
+
+ // Only the upper right and bottom left quadrants are kept.
+ const dataChecker = (a: Float32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ const is_x = idx % 2 === 0;
+ if (value === 0.0) {
+ return is_x ? a[idx + 1] === 0 : a[idx - 1] === 0;
+ }
+
+ let expect = is_x ? kWidth : kHeight;
+ expect = 0.5 * expect;
+ if (value < expect) {
+ return is_x ? a[idx + 1] > 0.5 * kWidth : a[idx - 1] > 0.5 * kHeight;
+ } else {
+ return is_x ? a[idx + 1] < 0.5 * kWidth : a[idx - 1] < 0.5 * kHeight;
+ }
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'data exp ==',
+ getValueForCell: (idx: number): number | string => {
+ if (idx < (kWidth * kHeight) / 2) {
+ return 'any';
+ } else {
+ return 0;
+ }
+ },
+ },
+ ],
+ }
+ );
+ };
+ const fbChecker = (a: Uint32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ const x = idx % kWidth;
+ const y = Math.floor(idx / kWidth);
+ if ((x >= kWidth / 2 && y >= kHeight / 2) || (x <= kWidth / 2 && y <= kHeight / 2)) {
+ return value === kWidth * kHeight;
+ } else {
+ return value < (kWidth * kHeight) / 2;
+ }
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'fb exp ==',
+ getValueForCell: (idx: number) => {
+ const x = idx % kWidth;
+ const y = Math.floor(idx / kWidth);
+ if (
+ (x <= kWidth / 2 && y <= kHeight / 2) ||
+ (x >= kWidth / 2 && y >= kHeight / 2)
+ ) {
+ return kWidth * kHeight;
+ }
+ return 'any';
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ drawFullScreen(t, code, dataChecker, fbChecker);
+ });
+
+g.test('loop')
+ .desc('Test discards in a loop')
+ .fn(t => {
+ const code = `
+${kSharedCode}
+
+@fragment
+fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 {
+ _ = uniformValues[0];
+ for (var i = 0; i < 2; i++) {
+ if i > 0 {
+ discard;
+ }
+ }
+ let idx = atomicAdd(&atomicIndex, 1);
+ output[idx] = pos.xy;
+ return 1;
+}
+`;
+
+ // No storage writes occur.
+ const dataChecker = (a: Float32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ return value === 0;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'data exp ==',
+ getValueForCell: (idx: number) => {
+ return 0;
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ // No fragment outputs occur.
+ const fbChecker = (a: Uint32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ return value === kWidth * kHeight;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'fb exp ==',
+ getValueForCell: (idx: number) => {
+ return kWidth * kHeight;
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ drawFullScreen(t, code, dataChecker, fbChecker);
+ });
+
+g.test('uniform_read_loop')
+ .desc('Test that helpers read a uniform value in a loop')
+ .fn(t => {
+ const code = `
+${kSharedCode}
+
+@fragment
+fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 {
+ discard;
+ for (var i = 0u; i < uniformValues[0]; i++) {
+ }
+ let idx = atomicAdd(&atomicIndex, 1);
+ output[idx] = pos.xy;
+ return 1;
+}
+`;
+
+ // No storage writes occur.
+ const dataChecker = (a: Float32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ return value === 0;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'data exp ==',
+ getValueForCell: (idx: number) => {
+ return 0;
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ // No fragment outputs occur.
+ const fbChecker = (a: Uint32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ return value === kWidth * kHeight;
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'fb exp ==',
+ getValueForCell: (idx: number) => {
+ return kWidth * kHeight;
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ drawFullScreen(t, code, dataChecker, fbChecker);
+ });
+
+g.test('derivatives')
+ .desc('Test that derivatives are correct in the presence of discard')
+ .fn(t => {
+ const code = `
+${kSharedCode}
+
+@fragment
+fn fsMain(@builtin(position) pos : vec4f) -> @location(0) u32 {
+ let ipos = vec2i(pos.xy);
+ let lsb = ipos & vec2(0x1);
+ let left_sel = select(2, 4, lsb.y == 1);
+ let right_sel = select(1, 3, lsb.y == 1);
+ let uidx = select(left_sel, right_sel, lsb.x == 1);
+ if ((lsb.x | lsb.y) & 0x1) == 0 {
+ discard;
+ }
+
+ let v = uniformValues[uidx];
+ let idx = atomicAdd(&atomicIndex, 1);
+ let dx = dpdx(f32(v));
+ let dy = dpdy(f32(v));
+ output[idx] = vec2(dx, dy);
+ return idx;
+}
+`;
+
+ // One pixel per quad is discarded. The derivatives values are always the same +/- 3.
+ const dataChecker = (a: Float32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ if (idx < (3 * (2 * kWidth * kHeight)) / 4) {
+ return value === -3 || value === 3;
+ } else {
+ return value === 0;
+ }
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'data exp ==',
+ getValueForCell: (idx: number) => {
+ if (idx < (3 * (2 * kWidth * kHeight)) / 4) {
+ return '+/- 3';
+ } else {
+ return 0;
+ }
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ // 3/4 of the fragments are written.
+ const fbChecker = (a: Uint32Array) => {
+ return checkElementsPassPredicate(
+ a,
+ (idx: number, value: number | bigint) => {
+ const x = idx % kWidth;
+ const y = Math.floor(idx / kWidth);
+ if (((x | y) & 0x1) === 0) {
+ return value === kWidth * kHeight;
+ } else {
+ return value < (3 * (kWidth * kHeight)) / 4;
+ }
+ },
+ {
+ predicatePrinter: [
+ {
+ leftHeader: 'fb exp ==',
+ getValueForCell: (idx: number) => {
+ const x = idx % kWidth;
+ const y = Math.floor(idx / kWidth);
+ if (((x | y) & 0x1) === 0) {
+ return kWidth * kHeight;
+ } else {
+ return 'any';
+ }
+ },
+ },
+ ],
+ }
+ );
+ };
+
+ drawFullScreen(t, code, dataChecker, fbChecker);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/zero_init.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/zero_init.spec.ts
index e03a72f8df..1c6c9bc4a3 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/zero_init.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/execution/zero_init.spec.ts
@@ -107,6 +107,10 @@ g.test('compute,zero_init')
? [true, false]
: [false]) {
for (const scalarType of supportedScalarTypes({ isAtomic, ...p })) {
+ // Fewer subcases: supportedScalarTypes was expanded to include f16
+ // but that may take too much time. It would require more complex code.
+ if (scalarType === 'f16') continue;
+
// Fewer subcases: For nested types, skip atomic u32 and non-atomic i32.
if (p._containerDepth > 0) {
if (scalarType === 'u32' && isAtomic) continue;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/types.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/types.ts
index 799ea3affb..76b094310d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/types.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/types.ts
@@ -2,19 +2,32 @@ import { keysOf } from '../../common/util/data_tables.js';
import { assert } from '../../common/util/util.js';
import { align } from '../util/math.js';
-const kArrayLength = 3;
+const kDefaultArrayLength = 3;
export type Requirement = 'never' | 'may' | 'must'; // never is the same as "must not"
-export type ContainerType = 'scalar' | 'vector' | 'matrix' | 'atomic' | 'array';
-export type ScalarType = 'i32' | 'u32' | 'f32' | 'bool';
+export type ContainerType = 'scalar' | 'vector' | 'matrix' | 'array';
+export type ScalarType = 'i32' | 'u32' | 'f16' | 'f32' | 'bool';
-export const HostSharableTypes = ['i32', 'u32', 'f32'] as const;
+export const HostSharableTypes = ['i32', 'u32', 'f16', 'f32'] as const;
+
+// The alignment and size of a host shareable type.
+// See "Alignment and Size" in the WGSL spec. https://w3.org/TR/WGSL/#alignment-and-size
+// Note this is independent of the address space that values of this type might appear in.
+// See RequiredAlignOf(...) for the 16-byte granularity requirement when
+// values of a type are placed in the uniform address space.
+type AlignmentAndSize = {
+ // AlignOf(T) for generated type T
+ alignment: number;
+ // SizeOf(T) for generated type T
+ size: number;
+};
/** Info for each plain scalar type. */
export const kScalarTypeInfo =
/* prettier-ignore */ {
'i32': { layout: { alignment: 4, size: 4 }, supportsAtomics: true, arrayLength: 1, innerLength: 0 },
'u32': { layout: { alignment: 4, size: 4 }, supportsAtomics: true, arrayLength: 1, innerLength: 0 },
+ 'f16': { layout: { alignment: 2, size: 2 }, supportsAtomics: false, arrayLength: 1, innerLength: 0, feature: 'shader-f16' },
'f32': { layout: { alignment: 4, size: 4 }, supportsAtomics: false, arrayLength: 1, innerLength: 0 },
'bool': { layout: undefined, supportsAtomics: false, arrayLength: 1, innerLength: 0 },
} as const;
@@ -24,29 +37,71 @@ export const kScalarTypes = keysOf(kScalarTypeInfo);
/** Info for each vecN<> container type. */
export const kVectorContainerTypeInfo =
/* prettier-ignore */ {
- 'vec2': { layout: { alignment: 8, size: 8 }, arrayLength: 2 , innerLength: 0 },
- 'vec3': { layout: { alignment: 16, size: 12 }, arrayLength: 3 , innerLength: 0 },
- 'vec4': { layout: { alignment: 16, size: 16 }, arrayLength: 4 , innerLength: 0 },
+ 'vec2': { arrayLength: 2 , innerLength: 0 },
+ 'vec3': { arrayLength: 3 , innerLength: 0 },
+ 'vec4': { arrayLength: 4 , innerLength: 0 },
} as const;
/** List of all vecN<> container types. */
export const kVectorContainerTypes = keysOf(kVectorContainerTypeInfo);
+/** Returns the vector layout for a given vector container and base type, or undefined if that base type has no layout */
+function vectorLayout(
+ vectorContainer: 'vec2' | 'vec3' | 'vec4',
+ baseType: ScalarType
+): undefined | AlignmentAndSize {
+ const n = kVectorContainerTypeInfo[vectorContainer].arrayLength;
+ const scalarLayout = kScalarTypeInfo[baseType].layout;
+ if (scalarLayout === undefined) {
+ return undefined;
+ }
+ if (n === 3) {
+ return { alignment: scalarLayout.alignment * 4, size: scalarLayout.size * 3 };
+ }
+ return { alignment: scalarLayout.alignment * n, size: scalarLayout.size * n };
+}
+
/** Info for each matNxN<> container type. */
export const kMatrixContainerTypeInfo =
/* prettier-ignore */ {
- 'mat2x2': { layout: { alignment: 8, size: 16 }, arrayLength: 2, innerLength: 2 },
- 'mat3x2': { layout: { alignment: 8, size: 24 }, arrayLength: 3, innerLength: 2 },
- 'mat4x2': { layout: { alignment: 8, size: 32 }, arrayLength: 4, innerLength: 2 },
- 'mat2x3': { layout: { alignment: 16, size: 32 }, arrayLength: 2, innerLength: 3 },
- 'mat3x3': { layout: { alignment: 16, size: 48 }, arrayLength: 3, innerLength: 3 },
- 'mat4x3': { layout: { alignment: 16, size: 64 }, arrayLength: 4, innerLength: 3 },
- 'mat2x4': { layout: { alignment: 16, size: 32 }, arrayLength: 2, innerLength: 4 },
- 'mat3x4': { layout: { alignment: 16, size: 48 }, arrayLength: 3, innerLength: 4 },
- 'mat4x4': { layout: { alignment: 16, size: 64 }, arrayLength: 4, innerLength: 4 },
+ 'mat2x2': { arrayLength: 2, innerLength: 2 },
+ 'mat3x2': { arrayLength: 3, innerLength: 2 },
+ 'mat4x2': { arrayLength: 4, innerLength: 2 },
+ 'mat2x3': { arrayLength: 2, innerLength: 3 },
+ 'mat3x3': { arrayLength: 3, innerLength: 3 },
+ 'mat4x3': { arrayLength: 4, innerLength: 3 },
+ 'mat2x4': { arrayLength: 2, innerLength: 4 },
+ 'mat3x4': { arrayLength: 3, innerLength: 4 },
+ 'mat4x4': { arrayLength: 4, innerLength: 4 },
} as const;
/** List of all matNxN<> container types. */
export const kMatrixContainerTypes = keysOf(kMatrixContainerTypeInfo);
+export const kMatrixContainerTypeLayoutInfo =
+ /* prettier-ignore */ {
+ 'f16': {
+ 'mat2x2': { layout: { alignment: 4, size: 8 } },
+ 'mat3x2': { layout: { alignment: 4, size: 12 } },
+ 'mat4x2': { layout: { alignment: 4, size: 16 } },
+ 'mat2x3': { layout: { alignment: 8, size: 16 } },
+ 'mat3x3': { layout: { alignment: 8, size: 24 } },
+ 'mat4x3': { layout: { alignment: 8, size: 32 } },
+ 'mat2x4': { layout: { alignment: 8, size: 16 } },
+ 'mat3x4': { layout: { alignment: 8, size: 24 } },
+ 'mat4x4': { layout: { alignment: 8, size: 32 } },
+ },
+ 'f32': {
+ 'mat2x2': { layout: { alignment: 8, size: 16 } },
+ 'mat3x2': { layout: { alignment: 8, size: 24 } },
+ 'mat4x2': { layout: { alignment: 8, size: 32 } },
+ 'mat2x3': { layout: { alignment: 16, size: 32 } },
+ 'mat3x3': { layout: { alignment: 16, size: 48 } },
+ 'mat4x3': { layout: { alignment: 16, size: 64 } },
+ 'mat2x4': { layout: { alignment: 16, size: 32 } },
+ 'mat3x4': { layout: { alignment: 16, size: 48 } },
+ 'mat4x4': { layout: { alignment: 16, size: 64 } },
+ }
+} as const;
+
export type AddressSpace = 'storage' | 'uniform' | 'private' | 'function' | 'workgroup' | 'handle';
export type AccessMode = 'read' | 'write' | 'read_write';
export type Scope = 'module' | 'function';
@@ -161,10 +216,39 @@ export function* generateTypes({
containerType: ContainerType;
/** Whether to wrap the baseType in `atomic<>`. */
isAtomic?: boolean;
-}) {
+}): Generator<
+ {
+ /** WGSL name for the generated type */
+ type: string;
+ _kTypeInfo: {
+ /**
+ * WGSL name for:
+ * - the generated type if it is scalar or atomic
+ * - the column vector type if the generated type is a matrix
+ * - the base type if the generated type is an array
+ */
+ elementBaseType: string;
+ /** Layout details if host-shareable, and undefined otherwise. */
+ layout: undefined | AlignmentAndSize;
+ supportsAtomics: boolean;
+ /** The number of elementBaseType items in the container. */
+ arrayLength: number;
+ /**
+ * 0 for scalar and vector.
+ * For a matrix type, this is the number of rows in the matrix.
+ */
+ innerLength?: number;
+ };
+ },
+ void
+> {
const scalarInfo = kScalarTypeInfo[baseType];
if (isAtomic) {
assert(scalarInfo.supportsAtomics, 'type does not support atomics');
+ assert(
+ containerType === 'scalar' || containerType === 'array',
+ "can only generate atomic inner types with containerType 'scalar' or 'array'"
+ );
}
const scalarType = isAtomic ? `atomic<${baseType}>` : baseType;
@@ -189,21 +273,29 @@ export function* generateTypes({
for (const vectorType of kVectorContainerTypes) {
yield {
type: `${vectorType}<${scalarType}>`,
- _kTypeInfo: { elementBaseType: baseType, ...kVectorContainerTypeInfo[vectorType] },
+ _kTypeInfo: {
+ elementBaseType: baseType,
+ ...kVectorContainerTypeInfo[vectorType],
+ layout: vectorLayout(vectorType, scalarType as ScalarType),
+ supportsAtomics: false,
+ },
};
}
}
if (containerType === 'matrix') {
- // Matrices can only be f32.
- if (baseType === 'f32') {
+ // Matrices can only be f16 or f32.
+ if (baseType === 'f16' || baseType === 'f32') {
for (const matrixType of kMatrixContainerTypes) {
- const matrixInfo = kMatrixContainerTypeInfo[matrixType];
+ const matrixDimInfo = kMatrixContainerTypeInfo[matrixType];
+ const matrixLayoutInfo = kMatrixContainerTypeLayoutInfo[baseType][matrixType];
yield {
type: `${matrixType}<${scalarType}>`,
_kTypeInfo: {
- elementBaseType: `vec${matrixInfo.innerLength}<${scalarType}>`,
- ...matrixInfo,
+ elementBaseType: `vec${matrixDimInfo.innerLength}<${scalarType}>`,
+ ...matrixDimInfo,
+ ...matrixLayoutInfo,
+ supportsAtomics: false,
},
};
}
@@ -212,41 +304,57 @@ export function* generateTypes({
// Array types
if (containerType === 'array') {
+ let arrayElemType: string = scalarType;
+ let arrayElementCount: number = kDefaultArrayLength;
+ let supportsAtomics = scalarInfo.supportsAtomics;
+ let layout: undefined | AlignmentAndSize = undefined;
+ if (scalarInfo.layout) {
+ // Compute the layout of the array type.
+ // Adjust the array element count or element type as needed.
+ if (addressSpace === 'uniform') {
+ // Use a vec4 of the scalar type, to achieve a 16 byte alignment without internal padding.
+ // This works for 4-byte scalar types, and does not work for f16.
+ // It is the caller's responsibility to filter out the f16 case.
+ assert(!isAtomic, 'the uniform case is making vec4 of scalar, which cannot handle atomics');
+ arrayElemType = `vec4<${baseType}>`;
+ supportsAtomics = false;
+ const arrayElemLayout = vectorLayout('vec4', baseType) as AlignmentAndSize;
+ // assert(arrayElemLayout.alignment % 16 === 0); // Callers responsibility to avoid
+ arrayElementCount = align(arrayElementCount, 4) / 4;
+ const arrayByteSize = arrayElementCount * arrayElemLayout.size;
+ layout = { alignment: arrayElemLayout.alignment, size: arrayByteSize };
+ } else {
+ // The ordinary case. Use scalarType as the element type.
+ const stride = arrayStride(scalarInfo.layout);
+ let arrayByteSize = arrayElementCount * stride;
+ if (addressSpace === 'storage') {
+ // The buffer effective binding size must be a multiple of 4.
+ // Adjust the array element count as needed.
+ while (arrayByteSize % 4 > 0) {
+ arrayElementCount++;
+ arrayByteSize = arrayElementCount * stride;
+ }
+ }
+ layout = { alignment: scalarInfo.layout.alignment, size: arrayByteSize };
+ }
+ }
+
const arrayTypeInfo = {
elementBaseType: `${baseType}`,
- arrayLength: kArrayLength,
- layout: scalarInfo.layout
- ? {
- alignment: scalarInfo.layout.alignment,
- size:
- addressSpace === 'uniform'
- ? // Uniform storage class must have array elements aligned to 16.
- kArrayLength *
- arrayStride({
- ...scalarInfo.layout,
- alignment: 16,
- })
- : kArrayLength * arrayStride(scalarInfo.layout),
- }
- : undefined,
+ arrayLength: arrayElementCount,
+ layout,
+ supportsAtomics,
};
// Sized
- if (addressSpace === 'uniform') {
- yield {
- type: `array<vec4<${scalarType}>,${kArrayLength}>`,
- _kTypeInfo: arrayTypeInfo,
- };
- } else {
- yield { type: `array<${scalarType},${kArrayLength}>`, _kTypeInfo: arrayTypeInfo };
- }
+ yield { type: `array<${arrayElemType},${arrayElementCount}>`, _kTypeInfo: arrayTypeInfo };
// Unsized
if (addressSpace === 'storage') {
- yield { type: `array<${scalarType}>`, _kTypeInfo: arrayTypeInfo };
+ yield { type: `array<${arrayElemType}>`, _kTypeInfo: arrayTypeInfo };
}
}
- function arrayStride(elementLayout: { size: number; alignment: number }) {
+ function arrayStride(elementLayout: AlignmentAndSize) {
return align(elementLayout.size, elementLayout.alignment);
}
@@ -272,7 +380,7 @@ export function supportsAtomics(p: {
);
}
-/** Generates an iterator of supported base types (i32/u32/f32/bool) */
+/** Generates an iterator of supported base types (i32/u32/f16/f32/bool) */
export function* supportedScalarTypes(p: { isAtomic: boolean; addressSpace: string }) {
for (const scalarType of kScalarTypes) {
const info = kScalarTypeInfo[scalarType];
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/const_assert/const_assert.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/const_assert/const_assert.spec.ts
index caaabc54d3..6c747f74a7 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/const_assert/const_assert.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/const_assert/const_assert.spec.ts
@@ -59,9 +59,9 @@ g.test('constant_expression_no_assert')
.desc(`Test that const_assert does not assert on a true conditional expression`)
.params(u =>
u
- .combine('case', keysOf(kConditionCases))
.combine('scope', ['module', 'function'] as const)
.beginSubcases()
+ .combine('case', keysOf(kConditionCases))
)
.fn(t => {
const expr = kConditionCases[t.params.case].expr;
@@ -76,9 +76,9 @@ g.test('constant_expression_assert')
.desc(`Test that const_assert does assert on a false conditional expression`)
.params(u =>
u
- .combine('case', keysOf(kConditionCases))
.combine('scope', ['module', 'function'] as const)
.beginSubcases()
+ .combine('case', keysOf(kConditionCases))
)
.fn(t => {
const expr = kConditionCases[t.params.case].expr;
@@ -95,10 +95,10 @@ g.test('constant_expression_logical_or_no_assert')
)
.params(u =>
u
- .combine('lhs', keysOf(kConditionCases))
- .combine('rhs', keysOf(kConditionCases))
.combine('scope', ['module', 'function'] as const)
.beginSubcases()
+ .combine('lhs', keysOf(kConditionCases))
+ .combine('rhs', keysOf(kConditionCases))
)
.fn(t => {
const expr = `(${kConditionCases[t.params.lhs].expr}) || (${
@@ -117,10 +117,10 @@ g.test('constant_expression_logical_or_assert')
)
.params(u =>
u
- .combine('lhs', keysOf(kConditionCases))
- .combine('rhs', keysOf(kConditionCases))
.combine('scope', ['module', 'function'] as const)
.beginSubcases()
+ .combine('lhs', keysOf(kConditionCases))
+ .combine('rhs', keysOf(kConditionCases))
)
.fn(t => {
const expr = `(${kConditionCases[t.params.lhs].expr}) || (${
@@ -139,10 +139,10 @@ g.test('constant_expression_logical_and_no_assert')
)
.params(u =>
u
- .combine('lhs', keysOf(kConditionCases))
- .combine('rhs', keysOf(kConditionCases))
.combine('scope', ['module', 'function'] as const)
.beginSubcases()
+ .combine('lhs', keysOf(kConditionCases))
+ .combine('rhs', keysOf(kConditionCases))
)
.fn(t => {
const expr = `(${kConditionCases[t.params.lhs].expr}) && (${
@@ -161,10 +161,10 @@ g.test('constant_expression_logical_and_assert')
)
.params(u =>
u
- .combine('lhs', keysOf(kConditionCases))
- .combine('rhs', keysOf(kConditionCases))
.combine('scope', ['module', 'function'] as const)
.beginSubcases()
+ .combine('lhs', keysOf(kConditionCases))
+ .combine('rhs', keysOf(kConditionCases))
)
.fn(t => {
const expr = `(${kConditionCases[t.params.lhs].expr}) && (${
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/compound_statement.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/compound_statement.spec.ts
new file mode 100644
index 0000000000..8ad89f48a8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/compound_statement.spec.ts
@@ -0,0 +1,98 @@
+export const description = `
+Validation tests for declarations in compound statements.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// 9.1 Compound Statements
+// When a declaration is one of those statements, its identifier is in scope from
+// the start of the next statement until the end of the compound statement.
+//
+// Enumerating cases: Consider a declaration X inside a compound statement.
+// The X declaration should be tested with potential uses and potentially
+// conflicting declarations in positions [a, b, c, d, e], in the following:
+// a { b; X; c; { d; } } e;
+
+const kConflictTests = {
+ a: {
+ src: 'let x = 1; { let x = 1; }',
+ pass: true,
+ },
+ bc: {
+ src: '{let x = 1; let x = 1; }',
+ pass: false,
+ },
+ d: {
+ src: '{let x = 1; { let x = 1; }}',
+ pass: true,
+ },
+ e: {
+ src: '{let x = 1; } let x = 1;',
+ pass: true,
+ },
+};
+
+g.test('decl_conflict')
+ .desc(
+ 'Test a potentially conflicting declaration relative to a declaration in a compound statement'
+ )
+ .params(u => u.combine('case', keysOf(kConflictTests)))
+ .fn(t => {
+ const wgsl = `
+@vertex fn vtx() -> @builtin(position) vec4f {
+ ${kConflictTests[t.params.case].src}
+ return vec4f(1);
+}`;
+ t.expectCompileResult(kConflictTests[t.params.case].pass, wgsl);
+ });
+
+const kUseTests = {
+ a: {
+ src: 'let y = x; { let x = 1; }',
+ pass: false, // not visible
+ },
+ b: {
+ src: '{ let y = x; let x = 1; }',
+ pass: false, // not visible
+ },
+ self: {
+ src: '{ let x = (x);}',
+ pass: false, // not visible
+ },
+ c_yes: {
+ src: '{ const x = 1; const_assert x == 1; }',
+ pass: true,
+ },
+ c_no: {
+ src: '{ const x = 1; const_assert x == 2; }',
+ pass: false,
+ },
+ d_yes: {
+ src: '{ const x = 1; { const_assert x == 1; }}',
+ pass: true,
+ },
+ d_no: {
+ src: '{ const x = 1; { const_assert x == 2; }}',
+ pass: false,
+ },
+ e: {
+ src: '{ const x = 1; } let y = x;',
+ pass: false, // not visible
+ },
+};
+
+g.test('decl_use')
+ .desc('Test a use of a declaration in a compound statement')
+ .params(u => u.combine('case', keysOf(kUseTests)))
+ .fn(t => {
+ const wgsl = `
+@vertex fn vtx() -> @builtin(position) vec4f {
+ ${kUseTests[t.params.case].src}
+ return vec4f(1);
+}`;
+ t.expectCompileResult(kUseTests[t.params.case].pass, wgsl);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/const.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/const.spec.ts
index 6ded2480c7..38ac76fa04 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/const.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/const.spec.ts
@@ -3,6 +3,7 @@ Validation tests for const declarations
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
import { ShaderValidationTest } from '../shader_validation_test.js';
export const g = makeTestGroup(ShaderValidationTest);
@@ -59,3 +60,160 @@ const b = S(4).a;
`;
t.expectCompileResult(t.params.target === 'a', wgsl);
});
+
+const kTypeCases = {
+ bool: {
+ code: `const x : bool = true;`,
+ valid: true,
+ },
+ i32: {
+ code: `const x : i32 = 1i;`,
+ valid: true,
+ },
+ u32: {
+ code: `const x : u32 = 1u;`,
+ valid: true,
+ },
+ f32: {
+ code: `const x : f32 = 1f;`,
+ valid: true,
+ },
+ f16: {
+ code: `enable f16;\nconst x : f16 = 1h;`,
+ valid: true,
+ },
+ abstract_int: {
+ code: `
+ const x = 0xffffffffff;
+ const_assert x == 0xffffffffff;`,
+ valid: true,
+ },
+ abstract_float: {
+ code: `
+ const x = 3937509.87755102;
+ const_assert x != 3937510.0;
+ const_assert x != 3937509.75;`,
+ valid: true,
+ },
+ vec2i: {
+ code: `const x : vec2i = vec2i();`,
+ valid: true,
+ },
+ vec3u: {
+ code: `const x : vec3u = vec3u();`,
+ valid: true,
+ },
+ vec4f: {
+ code: `const x : vec4f = vec4f();`,
+ valid: true,
+ },
+ mat2x2: {
+ code: `const x : mat2x2f = mat2x2f();`,
+ valid: true,
+ },
+ mat4x3f: {
+ code: `const x : mat4x3<f32> = mat4x3<f32>();`,
+ valid: true,
+ },
+ array_sized: {
+ code: `const x : array<u32, 4> = array(1,2,3,4);`,
+ valid: true,
+ },
+ array_runtime: {
+ code: `const x : array<u32> = array(1,2,3);`,
+ valid: false,
+ },
+ struct: {
+ code: `struct S { x : u32 }\nconst x : S = S(0);`,
+ valid: true,
+ },
+ atomic: {
+ code: `const x : atomic<u32> = 0;`,
+ valid: false,
+ },
+ vec_abstract_int: {
+ code: `
+ const x = vec2(0xffffffffff,0xfffffffff0);
+ const_assert x.x == 0xffffffffff;
+ const_assert x.y == 0xfffffffff0;`,
+ valid: true,
+ },
+ array_abstract_int: {
+ code: `
+ const x = array(0xffffffffff,0xfffffffff0);
+ const_assert x[0] == 0xffffffffff;
+ const_assert x[1] == 0xfffffffff0;`,
+ valid: true,
+ },
+};
+
+g.test('type')
+ .desc('Test const types')
+ .params(u => u.combine('case', keysOf(kTypeCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.case === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const testcase = kTypeCases[t.params.case];
+ const code = testcase.code;
+ const expect = testcase.valid;
+ t.expectCompileResult(expect, code);
+ });
+
+const kInitCases = {
+ no_init: {
+ code: `const x : u32;`,
+ valid: false,
+ },
+ no_type: {
+ code: `const x = 0;`,
+ valid: true,
+ },
+ init_matching_type: {
+ code: `const x : i32 = 1i;`,
+ valid: true,
+ },
+ init_mismatch_type: {
+ code: `const x : u32 = 1i;`,
+ valid: false,
+ },
+ abs_int_init_convert: {
+ code: `const x : u32 = 1;`,
+ valid: true,
+ },
+ abs_float_init_convert: {
+ code: `const x : f32 = 1.0;`,
+ valid: true,
+ },
+ init_const_expr: {
+ code: `const x = 0;\nconst y = x + 2;`,
+ valid: true,
+ },
+ init_override_expr: {
+ code: `override x : u32;\nconst y = x * 2;`,
+ valid: false,
+ },
+ init_runtime_expr: {
+ code: `var<private> x = 1i;\nconst y = x - 1;`,
+ valid: false,
+ },
+};
+
+g.test('initializer')
+ .desc('Test const initializers')
+ .params(u => u.combine('case', keysOf(kInitCases)))
+ .fn(t => {
+ const testcase = kInitCases[t.params.case];
+ const code = testcase.code;
+ const expect = testcase.valid;
+ t.expectCompileResult(expect, code);
+ });
+
+g.test('function_scope')
+ .desc('Test that const declarations are allowed in functions')
+ .fn(t => {
+ const code = `fn foo() { const x = 0; }`;
+ t.expectCompileResult(true, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/context_dependent_resolution.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/context_dependent_resolution.spec.ts
new file mode 100644
index 0000000000..aedef043cd
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/context_dependent_resolution.spec.ts
@@ -0,0 +1,338 @@
+export const description = `
+Tests that context dependent names do not participate in name resolution.
+That is, a declaration named the same as a context dependent name will not interfere.
+
+Context-dependent names:
+ * Attribute names
+ * Built-in value names
+ * Diagnostic severity control
+ * Diagnostic triggering rules
+ * Enable extensions
+ * Language extensions
+ * Swizzles
+ * Interpolation type
+ * Interpolation sampling
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kAttributeCases = {
+ align: `struct S { @align(16) x : u32 }`,
+ binding: `@group(0) @binding(0) var s : sampler;`,
+ builtin: `@vertex fn main() -> @builtin(position) vec4f { return vec4f(); }`,
+ // const is not writable
+ // diagnostic is a keyword
+ group: `@group(0) @binding(0) var s : sampler;`,
+ id: `@id(1) override x : i32;`,
+ interpolate: `@fragment fn main(@location(0) @interpolate(flat) x : i32) { }`,
+ invariant: `@fragment fn main(@builtin(position) @invariant pos : vec4f) { }`,
+ location: `@fragment fn main(@location(0) x : f32) { }`,
+ must_use: `@must_use fn foo() -> u32 { return 0; }`,
+ size: `struct S { @size(4) x : u32 }`,
+ workgroup_size: `@compute @workgroup_size(1) fn main() { }`,
+ compute: `@compute @workgroup_size(1) fn main() { }`,
+ fragment: `@fragment fn main() { }`,
+ vertex: `@vertex fn main() -> @builtin(position) vec4f { return vec4f(); }`,
+};
+
+g.test('attribute_names')
+ .desc('Tests attribute names do not use name resolution')
+ .params(u =>
+ u
+ .combine('case', keysOf(kAttributeCases))
+ .beginSubcases()
+ .combine('decl', ['override', 'const', 'var<private>'] as const)
+ )
+ .fn(t => {
+ const code = `
+ ${t.params.decl} ${t.params.case} : u32 = 0;
+ ${kAttributeCases[t.params.case]}
+ fn use_var() -> u32 {
+ return ${t.params.case};
+ }
+ `;
+
+ t.expectCompileResult(true, code);
+ });
+
+const kBuiltinCases = {
+ vertex_index: `
+ @vertex
+ fn main(@builtin(vertex_index) idx : u32) -> @builtin(position) vec4f
+ { return vec4f(); }`,
+ instance_index: `
+ @vertex
+ fn main(@builtin(instance_index) idx : u32) -> @builtin(position) vec4f
+ { return vec4f(); }`,
+ position_vertex: `
+ @vertex fn main() -> @builtin(position) vec4f
+ { return vec4f(); }`,
+ position_fragment: `@fragment fn main(@builtin(position) pos : vec4f) { }`,
+ front_facing: `@fragment fn main(@builtin(front_facing) x : bool) { }`,
+ frag_depth: `@fragment fn main() -> @builtin(frag_depth) f32 { return 0; }`,
+ sample_index: `@fragment fn main(@builtin(sample_index) x : u32) { }`,
+ sample_mask_input: `@fragment fn main(@builtin(sample_mask) x : u32) { }`,
+ sample_mask_output: `@fragment fn main() -> @builtin(sample_mask) u32 { return 0; }`,
+ local_invocation_id: `
+ @compute @workgroup_size(1)
+ fn main(@builtin(local_invocation_id) id : vec3u) { }`,
+ local_invocation_index: `
+ @compute @workgroup_size(1)
+ fn main(@builtin(local_invocation_index) id : u32) { }`,
+ global_invocation_id: `
+ @compute @workgroup_size(1)
+ fn main(@builtin(global_invocation_id) id : vec3u) { }`,
+ workgroup_id: `
+ @compute @workgroup_size(1)
+ fn main(@builtin(workgroup_id) id : vec3u) { }`,
+ num_workgroups: `
+ @compute @workgroup_size(1)
+ fn main(@builtin(num_workgroups) id : vec3u) { }`,
+};
+
+g.test('builtin_value_names')
+ .desc('Tests builtin value names do not use name resolution')
+ .params(u =>
+ u
+ .combine('case', keysOf(kBuiltinCases))
+ .beginSubcases()
+ .combine('decl', ['override', 'const', 'var<private>'] as const)
+ )
+ .fn(t => {
+ const code = `
+ ${t.params.decl} ${t.params.case} : u32 = 0;
+ ${kBuiltinCases[t.params.case]}
+ fn use_var() -> u32 {
+ return ${t.params.case};
+ }
+ `;
+
+ t.expectCompileResult(true, code);
+ });
+
+const kDiagnosticSeverityCases = {
+ error: `
+ diagnostic(error, derivative_uniformity);
+ @diagnostic(error, derivative_uniformity) fn foo() { }
+ `,
+ warning: `
+ diagnostic(warning, derivative_uniformity);
+ @diagnostic(warning, derivative_uniformity) fn foo() { }
+ `,
+ off: `
+ diagnostic(off, derivative_uniformity);
+ @diagnostic(off, derivative_uniformity) fn foo() { }
+ `,
+ info: `
+ diagnostic(info, derivative_uniformity);
+ @diagnostic(info, derivative_uniformity) fn foo() { }
+ `,
+};
+
+g.test('diagnostic_severity_names')
+ .desc('Tests diagnostic severity names do not use name resolution')
+ .params(u =>
+ u
+ .combine('case', keysOf(kDiagnosticSeverityCases))
+ .beginSubcases()
+ .combine('decl', ['override', 'const', 'var<private>'] as const)
+ )
+ .fn(t => {
+ const code = `
+ ${kDiagnosticSeverityCases[t.params.case]}
+ ${t.params.decl} ${t.params.case} : u32 = 0;
+ fn use_var() -> u32 {
+ return ${t.params.case};
+ }
+ `;
+
+ t.expectCompileResult(true, code);
+ });
+
+const kDiagnosticRuleCases = {
+ derivative_uniformity: `
+ diagnostic(off, derivative_uniformity);
+ @diagnostic(warning, derivative_uniformity) fn foo() { }`,
+ unknown_rule: `
+ diagnostic(off, unknown_rule);
+ @diagnostic(warning, unknown_rule) fn foo() { }`,
+ unknown: `
+ diagnostic(off, unknown.rule);
+ @diagnostic(warning, unknown.rule) fn foo() { }`,
+ rule: `
+ diagnostic(off, unknown.rule);
+ @diagnostic(warning, unknown.rule) fn foo() { }`,
+};
+
+g.test('diagnostic_rule_names')
+ .desc('Tests diagnostic rule names do not use name resolution')
+ .params(u =>
+ u
+ .combine('case', keysOf(kDiagnosticRuleCases))
+ .beginSubcases()
+ .combine('decl', ['override', 'const', 'var<private>'] as const)
+ )
+ .fn(t => {
+ const code = `
+ ${kDiagnosticRuleCases[t.params.case]}
+ ${t.params.decl} ${t.params.case} : u32 = 0;
+ fn use_var() -> u32 {
+ return ${t.params.case};
+ }
+ `;
+
+ t.expectCompileResult(true, code);
+ });
+
+const kEnableCases = {
+ f16: `enable f16;`,
+};
+
+g.test('enable_names')
+ .desc('Tests enable extension names do not use name resolution')
+ .params(u =>
+ u
+ .combine('case', keysOf(kEnableCases))
+ .beginSubcases()
+ .combine('decl', ['override', 'const', 'var<private>'] as const)
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ })
+ .fn(t => {
+ const code = `
+ ${kEnableCases[t.params.case]}
+ ${t.params.decl} ${t.params.case} : u32 = 0;
+ fn use_var() -> u32 {
+ return ${t.params.case};
+ }
+ `;
+
+ t.expectCompileResult(true, code);
+ });
+
+const kLanguageCases = {
+ readonly_and_readwrite_storage_textures: `requires readonly_and_readwrite_storage_textures;`,
+ packed_4x8_integer_dot_product: `requires packed_4x8_integer_dot_product;`,
+ unrestricted_pointer_parameters: `requires unrestricted_pointer_parameters;`,
+ pointer_composite_access: `requires pointer_composite_access;`,
+};
+
+g.test('language_names')
+ .desc('Tests language extension names do not use name resolution')
+ .params(u =>
+ u
+ .combine('case', keysOf(kLanguageCases))
+ .beginSubcases()
+ .combine('decl', ['override', 'const', 'var<private>'] as const)
+ )
+ .fn(t => {
+ t.skipIf(!t.hasLanguageFeature(t.params.case), 'Missing language feature');
+ const code = `
+ ${kLanguageCases[t.params.case]}
+ ${t.params.decl} ${t.params.case} : u32 = 0;
+ fn use_var() -> u32 {
+ return ${t.params.case};
+ }
+ `;
+
+ t.expectCompileResult(true, code);
+ });
+
+const kSwizzleCases = [
+ 'x',
+ 'y',
+ 'z',
+ 'w',
+ 'xy',
+ 'yxz',
+ 'wxyz',
+ 'xyxy',
+ 'r',
+ 'g',
+ 'b',
+ 'a',
+ 'rgb',
+ 'arr',
+ 'bgra',
+ 'agra',
+];
+
+g.test('swizzle_names')
+ .desc('Tests swizzle names do not use name resolution')
+ .params(u =>
+ u
+ .combine('case', kSwizzleCases)
+ .beginSubcases()
+ .combine('decl', ['override', 'const', 'var<private>'] as const)
+ )
+ .fn(t => {
+ let code = `${t.params.decl} ${t.params.case} : u32 = 0;\n`;
+ if (t.params.case.length === 1) {
+ for (let i = 2; i <= 4; i++) {
+ code += `${t.params.decl} ${t.params.case.padEnd(i, t.params.case[0])} : u32 = 0;\n`;
+ }
+ }
+ code += `fn foo() {
+ var x : vec4f;
+ _ = x.${t.params.case};
+ `;
+ if (t.params.case.length === 1) {
+ for (let i = 2; i <= 4; i++) {
+ code += `_ = x.${t.params.case.padEnd(i, t.params.case[0])};\n`;
+ }
+ }
+ code += `}
+ fn use_var() -> u32 {
+ return ${t.params.case};
+ }`;
+ t.expectCompileResult(true, code);
+ });
+
+const kInterpolationTypeCases = ['perspective', 'linear', 'flat'];
+
+g.test('interpolation_type_names')
+ .desc('Tests interpolation type names do not use name resolution')
+ .params(u =>
+ u
+ .combine('case', kInterpolationTypeCases)
+ .beginSubcases()
+ .combine('decl', ['override', 'const', 'var<private>'] as const)
+ )
+ .fn(t => {
+ const code = `
+ ${t.params.decl} ${t.params.case} : u32 = 0;
+ @fragment fn main(@location(0) @interpolate(${t.params.case}) x : f32) { }
+ fn use_var() -> u32 {
+ return ${t.params.case};
+ }
+ `;
+
+ t.expectCompileResult(true, code);
+ });
+
+const kInterpolationSamplingCases = ['center', 'centroid', 'sample'];
+
+g.test('interpolation_sampling_names')
+ .desc('Tests interpolation type names do not use name resolution')
+ .params(u =>
+ u
+ .combine('case', kInterpolationSamplingCases)
+ .beginSubcases()
+ .combine('decl', ['override', 'const', 'var<private>'] as const)
+ )
+ .fn(t => {
+ const code = `
+ ${t.params.decl} ${t.params.case} : u32 = 0;
+ @fragment fn main(@location(0) @interpolate(perspective, ${t.params.case}) x : f32) { }
+ fn use_var() -> u32 {
+ return ${t.params.case};
+ }
+ `;
+
+ t.expectCompileResult(true, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/let.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/let.spec.ts
new file mode 100644
index 0000000000..0e116a0ef4
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/let.spec.ts
@@ -0,0 +1,180 @@
+export const description = `
+Validation tests for let declarations
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+interface Case {
+ code: string;
+ valid: boolean;
+ decls?: string;
+}
+
+const kTypeCases: Record<string, Case> = {
+ bool: {
+ code: `let x : bool = true;`,
+ valid: true,
+ },
+ i32: {
+ code: `let x : i32 = 1i;`,
+ valid: true,
+ },
+ u32: {
+ code: `let x : u32 = 1u;`,
+ valid: true,
+ },
+ f32: {
+ code: `let x : f32 = 1f;`,
+ valid: true,
+ },
+ f16: {
+ code: `let x : f16 = 1h;`,
+ valid: true,
+ },
+ vec2i: {
+ code: `let x : vec2i = vec2i();`,
+ valid: true,
+ },
+ vec3u: {
+ code: `let x : vec3u = vec3u();`,
+ valid: true,
+ },
+ vec4f: {
+ code: `let x : vec4f = vec4f();`,
+ valid: true,
+ },
+ mat2x2: {
+ code: `let x : mat2x2f = mat2x2f();`,
+ valid: true,
+ },
+ mat4x3f: {
+ code: `let x : mat4x3<f32> = mat4x3<f32>();`,
+ valid: true,
+ },
+ array_sized: {
+ code: `let x : array<u32, 4> = array(1,2,3,4);`,
+ valid: true,
+ },
+ array_runtime: {
+ code: `let x : array<u32> = array(1,2,3);`,
+ valid: false,
+ },
+ struct: {
+ code: `let x : S = S(0);`,
+ valid: true,
+ decls: `struct S { x : u32 }`,
+ },
+ atomic: {
+ code: `let x : atomic<u32> = 0;`,
+ valid: false,
+ },
+ ptr_function: {
+ code: `
+ var x : i32;
+ let y : ptr<function, i32> = &x;`,
+ valid: true,
+ },
+ ptr_storage: {
+ code: `let y : ptr<storage, i32> = &x[0];`,
+ valid: true,
+ decls: `@group(0) @binding(0) var<storage> x : array<i32, 4>;`,
+ },
+ load_rule: {
+ code: `
+ var x : i32 = 1;
+ let y : i32 = x;`,
+ valid: true,
+ },
+};
+
+g.test('type')
+ .desc('Test let types')
+ .params(u => u.combine('case', keysOf(kTypeCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.case === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const testcase = kTypeCases[t.params.case];
+ const code = `
+${t.params.case === 'f16' ? 'enable f16;' : ''}
+${testcase.decls ?? ''}
+fn foo() {
+ ${testcase.code}
+}`;
+ const expect = testcase.valid;
+ t.expectCompileResult(expect, code);
+ });
+
+const kInitCases: Record<string, Case> = {
+ no_init: {
+ code: `let x : u32;`,
+ valid: false,
+ },
+ no_type: {
+ code: `let x = 1;`,
+ valid: true,
+ },
+ init_matching_type: {
+ code: `let x : u32 = 1u;`,
+ valid: true,
+ },
+ init_mismatch_type: {
+ code: `let x : u32 = 1i;`,
+ valid: false,
+ },
+ ptr_type_mismatch: {
+ code: `var x : i32;\nlet y : ptr<function, u32> = &x;`,
+ valid: false,
+ },
+ ptr_access_mismatch: {
+ code: `let y : ptr<storage, u32, read> = &x;`,
+ valid: false,
+ decls: `@group(0) @binding(0) var<storage, read_write> x : u32;`,
+ },
+ ptr_addrspace_mismatch: {
+ code: `let y = ptr<storage, u32> = &x;`,
+ valid: false,
+ decls: `@group(0) @binding(0) var<uniform> x : u32;`,
+ },
+ init_const_expr: {
+ code: `let y = x * 2;`,
+ valid: true,
+ decls: `const x = 1;`,
+ },
+ init_override_expr: {
+ code: `let y = x + 1;`,
+ valid: true,
+ decls: `override x = 1;`,
+ },
+ init_runtime_expr: {
+ code: `var x = 1;\nlet y = x << 1;`,
+ valid: true,
+ },
+};
+
+g.test('initializer')
+ .desc('Test let initializers')
+ .params(u => u.combine('case', keysOf(kInitCases)))
+ .fn(t => {
+ const testcase = kInitCases[t.params.case];
+ const code = `
+${testcase.decls ?? ''}
+fn foo() {
+ ${testcase.code}
+}`;
+ const expect = testcase.valid;
+ t.expectCompileResult(expect, code);
+ });
+
+g.test('module_scope')
+ .desc('Test that let declarations are disallowed module scope')
+ .fn(t => {
+ const code = `let x = 0;`;
+ t.expectCompileResult(false, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/override.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/override.spec.ts
index 82a35a2f59..41dd1391b2 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/override.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/override.spec.ts
@@ -3,6 +3,7 @@ Validation tests for override declarations
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
import { ShaderValidationTest } from '../shader_validation_test.js';
export const g = makeTestGroup(ShaderValidationTest);
@@ -29,3 +30,180 @@ override c : i32 = ${t.params.target};
`;
t.expectCompileResult(t.params.target === 'a', wgsl);
});
+
+const kIdCases = {
+ min: {
+ code: `@id(0) override x = 1;`,
+ valid: true,
+ },
+ max: {
+ code: `@id(65535) override x = 1;`,
+ valid: true,
+ },
+ neg: {
+ code: `@id(-1) override x = 1;`,
+ valid: false,
+ },
+ too_large: {
+ code: `@id(65536) override x = 1;`,
+ valid: false,
+ },
+ duplicate: {
+ code: `
+ @id(1) override x = 1;
+ @id(1) override y = 1;`,
+ valid: false,
+ },
+};
+
+g.test('id')
+ .desc('Test id attributes')
+ .params(u => u.combine('case', keysOf(kIdCases)))
+ .fn(t => {
+ const testcase = kIdCases[t.params.case];
+ const code = testcase.code;
+ const expect = testcase.valid;
+ t.expectCompileResult(expect, code);
+ });
+
+const kTypeCases = {
+ bool: {
+ code: `override x : bool;`,
+ valid: true,
+ },
+ i32: {
+ code: `override x : i32;`,
+ valid: true,
+ },
+ u32: {
+ code: `override x : u32;`,
+ valid: true,
+ },
+ f32: {
+ code: `override x : f32;`,
+ valid: true,
+ },
+ f16: {
+ code: `enable f16;\noverride x : f16;`,
+ valid: true,
+ },
+ abs_int_conversion: {
+ code: `override x = 1;`,
+ valid: true,
+ },
+ abs_float_conversion: {
+ code: `override x = 1.0;`,
+ valid: true,
+ },
+ vec2_bool: {
+ code: `override x : vec2<bool>;`,
+ valid: false,
+ },
+ vec2i: {
+ code: `override x : vec2i;`,
+ valid: false,
+ },
+ vec3u: {
+ code: `override x : vec3u;`,
+ valid: false,
+ },
+ vec4f: {
+ code: `override x : vec4f;`,
+ valid: false,
+ },
+ mat2x2f: {
+ code: `override x : mat2x2f;`,
+ valid: false,
+ },
+ matrix: {
+ code: `override x : mat4x3<f32>;`,
+ valid: false,
+ },
+ array: {
+ code: `override x : array<u32, 4>;`,
+ valid: false,
+ },
+ struct: {
+ code: `struct S { x : u32 }\noverride x : S;`,
+ valid: false,
+ },
+ atomic: {
+ code: `override x : atomic<u32>;`,
+ valid: false,
+ },
+};
+
+g.test('type')
+ .desc('Test override types')
+ .params(u => u.combine('case', keysOf(kTypeCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.case === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const testcase = kTypeCases[t.params.case];
+ const code = testcase.code;
+ const expect = testcase.valid;
+ t.expectCompileResult(expect, code);
+ });
+
+const kInitCases = {
+ no_init_no_type: {
+ code: `override x;`,
+ valid: false,
+ },
+ no_init: {
+ code: `override x : u32;`,
+ valid: true,
+ },
+ no_type: {
+ code: `override x = 1;`,
+ valid: true,
+ },
+ init_matching_type: {
+ code: `override x : u32 = 1u;`,
+ valid: true,
+ },
+ init_mismatch_type: {
+ code: `override x : u32 = 1i;`,
+ valid: false,
+ },
+ abs_int_init_convert: {
+ code: `override x : f32 = 1;`,
+ valid: true,
+ },
+ abs_float_init_convert: {
+ code: `override x : f32 = 1.0;`,
+ valid: true,
+ },
+ init_const_expr: {
+ code: `const x = 1;\noverride y = 2 * x;`,
+ valid: true,
+ },
+ init_override_expr: {
+ code: `override x = 1;\noverride y = x + 2;`,
+ valid: true,
+ },
+ init_runtime_expr: {
+ code: `var<private> x = 2;\noverride y = x;`,
+ valid: false,
+ },
+};
+
+g.test('initializer')
+ .desc('Test override initializers')
+ .params(u => u.combine('case', keysOf(kInitCases)))
+ .fn(t => {
+ const testcase = kInitCases[t.params.case];
+ const code = testcase.code;
+ const expect = testcase.valid;
+ t.expectCompileResult(expect, code);
+ });
+
+g.test('function_scope')
+ .desc('Test that override declarations are disallowed in functions')
+ .fn(t => {
+ const code = `fn foo() { override x : u32; }`;
+ t.expectCompileResult(false, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/var.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/var.spec.ts
new file mode 100644
index 0000000000..95e7417d23
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/decl/var.spec.ts
@@ -0,0 +1,529 @@
+export const description = `
+Validation tests for host-shareable types.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// The set of types and their properties.
+const kTypes = {
+ // Scalars.
+ bool: {
+ isHostShareable: false,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ i32: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ u32: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ f32: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ f16: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: true,
+ },
+
+ // Vectors.
+ 'vec2<bool>': {
+ isHostShareable: false,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ vec3i: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ vec4u: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ vec2f: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ vec3h: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: true,
+ },
+
+ // Matrices.
+ mat2x2f: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ mat3x4h: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: true,
+ },
+
+ // Atomics.
+ 'atomic<i32>': {
+ isHostShareable: true,
+ isConstructible: false,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ 'atomic<u32>': {
+ isHostShareable: true,
+ isConstructible: false,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+
+ // Arrays.
+ 'array<vec4<bool>>': {
+ isHostShareable: false,
+ isConstructible: false,
+ isFixedFootprint: false,
+ requiresF16: false,
+ },
+ 'array<vec4<bool>, 4>': {
+ isHostShareable: false,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ 'array<vec4u>': {
+ isHostShareable: true,
+ isConstructible: false,
+ isFixedFootprint: false,
+ requiresF16: false,
+ },
+ 'array<vec4u, 4>': {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ 'array<vec4u, array_size_const>': {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ 'array<vec4u, array_size_override>': {
+ isHostShareable: false,
+ isConstructible: false,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+
+ // Structures.
+ S_u32: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ S_bool: {
+ isHostShareable: false,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ S_S_bool: {
+ isHostShareable: false,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ S_array_vec4u: {
+ isHostShareable: true,
+ isConstructible: false,
+ isFixedFootprint: false,
+ requiresF16: false,
+ },
+ S_array_vec4u_4: {
+ isHostShareable: true,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+ S_array_bool_4: {
+ isHostShareable: false,
+ isConstructible: true,
+ isFixedFootprint: true,
+ requiresF16: false,
+ },
+
+ // Misc.
+ 'ptr<function, u32>': {
+ isHostShareable: false,
+ isConstructible: false,
+ isFixedFootprint: false,
+ requiresF16: false,
+ },
+ sampler: {
+ isHostShareable: false,
+ isConstructible: false,
+ isFixedFootprint: false,
+ requiresF16: false,
+ },
+ 'texture_2d<f32>': {
+ isHostShareable: false,
+ isConstructible: false,
+ isFixedFootprint: false,
+ requiresF16: false,
+ },
+};
+
+g.test('module_scope_types')
+ .desc('Test that only types that are allowed for a given address space are accepted.')
+ .params(u =>
+ u
+ .combine('type', keysOf(kTypes))
+ .combine('kind', [
+ 'comment',
+ 'handle',
+ 'private',
+ 'storage_ro',
+ 'storage_rw',
+ 'uniform',
+ 'workgroup',
+ ])
+ .combine('via_alias', [false, true])
+ )
+ .beforeAllSubcases(t => {
+ if (kTypes[t.params.type].requiresF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kTypes[t.params.type];
+ const isAtomic = t.params.type.indexOf('atomic') > -1;
+
+ let decl = '<>';
+ let shouldPass = false;
+ switch (t.params.kind) {
+ case 'comment':
+ // Control case to make sure all types are spelled correctly.
+ // We always emit an alias to the target type.
+ decl = '// ';
+ shouldPass = true;
+ break;
+ case 'handle':
+ decl = '@group(0) @binding(0) var foo : ';
+ shouldPass = t.params.type.indexOf('texture') > -1 || t.params.type.indexOf('sampler') > -1;
+ break;
+ case 'private':
+ decl = 'var<private> foo : ';
+ shouldPass = type.isConstructible;
+ break;
+ case 'storage_ro':
+ decl = '@group(0) @binding(0) var<storage, read> foo : ';
+ shouldPass = type.isHostShareable && !isAtomic;
+ break;
+ case 'storage_rw':
+ decl = '@group(0) @binding(0) var<storage, read_write> foo : ';
+ shouldPass = type.isHostShareable;
+ break;
+ case 'uniform':
+ decl = '@group(0) @binding(0) var<uniform> foo : ';
+ shouldPass = type.isHostShareable && type.isConstructible;
+ break;
+ case 'workgroup':
+ decl = 'var<workgroup> foo : ';
+ shouldPass = type.isFixedFootprint;
+ break;
+ }
+
+ const wgsl = `${type.requiresF16 ? 'enable f16;' : ''}
+ const array_size_const = 4;
+ override array_size_override = 4;
+
+ struct S_u32 { a : u32 }
+ struct S_bool { a : bool }
+ struct S_S_bool { a : S_bool }
+ struct S_array_vec4u { a : array<u32> }
+ struct S_array_vec4u_4 { a : array<vec4u, 4> }
+ struct S_array_bool_4 { a : array<bool, 4> }
+
+ alias MyType = ${t.params.type};
+
+ ${decl} ${t.params.via_alias ? 'MyType' : t.params.type};
+ `;
+
+ t.expectCompileResult(shouldPass, wgsl);
+ });
+
+g.test('function_scope_types')
+ .desc('Test that only types that are allowed for a given address space are accepted.')
+ .params(u =>
+ u
+ .combine('type', keysOf(kTypes))
+ .combine('kind', ['comment', 'var'])
+ .combine('via_alias', [false, true])
+ )
+ .beforeAllSubcases(t => {
+ if (kTypes[t.params.type].requiresF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kTypes[t.params.type];
+
+ let decl = '<>';
+ let shouldPass = false;
+ switch (t.params.kind) {
+ case 'comment':
+ // Control case to make sure all types are spelled correctly.
+ // We always emit an alias to the target type.
+ decl = '// ';
+ shouldPass = true;
+ break;
+ case 'var':
+ decl = 'var foo : ';
+ shouldPass = type.isConstructible;
+ break;
+ }
+
+ const wgsl = `${type.requiresF16 ? 'enable f16;' : ''}
+ const array_size_const = 4;
+ override array_size_override = 4;
+
+ struct S_u32 { a : u32 }
+ struct S_bool { a : bool }
+ struct S_S_bool { a : S_bool }
+ struct S_array_vec4u { a : array<u32> }
+ struct S_array_vec4u_4 { a : array<vec4u, 4> }
+ struct S_array_bool_4 { a : array<bool, 4> }
+
+ alias MyType = ${t.params.type};
+
+ fn foo() {
+ ${decl} ${t.params.via_alias ? 'MyType' : t.params.type};
+ }`;
+
+ t.expectCompileResult(shouldPass, wgsl);
+ });
+
+g.test('module_scope_initializers')
+ .desc('Test that initializers are only supported on address spaces that allow them.')
+ .params(u =>
+ u
+ .combine('initializer', [false, true])
+ .combine('kind', ['private', 'storage_ro', 'storage_rw', 'uniform', 'workgroup'])
+ )
+ .fn(t => {
+ let decl = '<>';
+ switch (t.params.kind) {
+ case 'private':
+ decl = 'var<private> foo : ';
+ break;
+ case 'storage_ro':
+ decl = '@group(0) @binding(0) var<storage, read> foo : ';
+ break;
+ case 'storage_rw':
+ decl = '@group(0) @binding(0) var<storage, read_write> foo : ';
+ break;
+ case 'uniform':
+ decl = '@group(0) @binding(0) var<uniform> foo : ';
+ break;
+ case 'workgroup':
+ decl = 'var<workgroup> foo : ';
+ break;
+ }
+
+ const wgsl = `${decl} u32${t.params.initializer ? ' = 42u' : ''};`;
+ t.expectCompileResult(t.params.kind === 'private' || !t.params.initializer, wgsl);
+ });
+
+g.test('handle_initializer')
+ .desc('Test that initializers are not allowed for handle types')
+ .params(u =>
+ u.combine('initializer', [false, true]).combine('type', ['sampler', 'texture_2d<f32>'])
+ )
+ .fn(t => {
+ const wgsl = `
+ @group(0) @binding(0) var foo : ${t.params.type};
+ @group(0) @binding(1) var bar : ${t.params.type}${t.params.initializer ? ' = foo' : ''};`;
+ t.expectCompileResult(!t.params.initializer, wgsl);
+ });
+
+// A list of u32 initializers and their validity for the private address space.
+const kInitializers = {
+ 'u32()': true,
+ '42u': true,
+ 'u32(sqrt(42.0))': true,
+ 'user_func()': false,
+ my_const_42u: true,
+ my_override_42u: true,
+ another_private_var: false,
+ 'vec4u(1, 2, 3, 4)[my_const_42u / 20]': true,
+ 'vec4u(1, 2, 3, 4)[my_override_42u / 20]': true,
+ 'vec4u(1, 2, 3, 4)[another_private_var / 20]': false,
+};
+
+g.test('initializer_kind')
+ .desc(
+ 'Test that initializers must be const-expression or override-expression for the private address space.'
+ )
+ .params(u =>
+ u.combine('initializer', keysOf(kInitializers)).combine('addrspace', ['private', 'function'])
+ )
+ .fn(t => {
+ let wgsl = `
+ const my_const_42u = 42u;
+ override my_override_42u : u32;
+ var<private> another_private_var = 42u;
+
+ fn user_func() -> u32 {
+ return 42u;
+ }
+ `;
+
+ if (t.params.addrspace === 'private') {
+ wgsl += `
+ var<private> foo : u32 = ${t.params.initializer};`;
+ } else {
+ wgsl += `
+ fn foo() {
+ var bar : u32 = ${t.params.initializer};
+ }`;
+ }
+ t.expectCompileResult(
+ t.params.addrspace === 'function' || kInitializers[t.params.initializer],
+ wgsl
+ );
+ });
+
+g.test('function_addrspace_at_module_scope')
+ .desc('Test that the function address space is not allowed at module scope.')
+ .params(u => u.combine('addrspace', ['private', 'function']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.addrspace === 'private',
+ `var<${t.params.addrspace}> foo : i32;`
+ );
+ });
+
+// A list of resource variable declarations.
+const kResourceDecls = {
+ uniform: 'var<uniform> buffer : vec4f;',
+ storage: 'var<storage> buffer : vec4f;',
+ texture: 'var t : texture_2d<f32>;',
+ sampler: 'var s : sampler;',
+};
+
+g.test('binding_point_on_resources')
+ .desc('Test that resource variables must have both @group and @binding attributes.')
+ .params(u =>
+ u
+ .combine('decl', keysOf(kResourceDecls))
+ .combine('group', ['', '@group(0)'])
+ .combine('binding', ['', '@binding(0)'])
+ )
+ .fn(t => {
+ const shouldPass = t.params.group !== '' && t.params.binding !== '';
+ const wgsl = `${t.params.group} ${t.params.binding} ${kResourceDecls[t.params.decl]}`;
+ t.expectCompileResult(shouldPass, wgsl);
+ });
+
+g.test('binding_point_on_non_resources')
+ .desc('Test that non-resource variables cannot have either @group or @binding attributes.')
+ .params(u =>
+ u
+ .combine('addrspace', ['private', 'workgroup'])
+ .combine('group', ['', '@group(0)'])
+ .combine('binding', ['', '@binding(0)'])
+ )
+ .fn(t => {
+ const shouldPass = t.params.group === '' && t.params.binding === '';
+ const wgsl = `${t.params.group} ${t.params.binding} var<${t.params.addrspace}> foo : i32;`;
+ t.expectCompileResult(shouldPass, wgsl);
+ });
+
+g.test('binding_point_on_function_var')
+ .desc('Test that function variables cannot have either @group or @binding attributes.')
+ .params(u => u.combine('group', ['', '@group(0)']).combine('binding', ['', '@binding(0)']))
+ .fn(t => {
+ const shouldPass = t.params.group === '' && t.params.binding === '';
+ const wgsl = `
+ fn foo() {
+ ${t.params.group} ${t.params.binding} var bar : i32;
+ }`;
+ t.expectCompileResult(shouldPass, wgsl);
+ });
+
+g.test('binding_collisions')
+ .desc('Test that binding points can collide iff they are not used by the same entry point.')
+ .params(u =>
+ u
+ .combine('a_group', [0, 1])
+ .combine('b_group', [0, 1])
+ .combine('a_binding', [0, 1])
+ .combine('b_binding', [0, 1])
+ .combine('b_use', ['same', 'different'])
+ )
+ .fn(t => {
+ const wgsl = `
+ @group(${t.params.a_group}) @binding(${t.params.a_binding}) var<uniform> a : vec4f;
+ @group(${t.params.b_group}) @binding(${t.params.b_binding}) var<uniform> b : vec4f;
+
+ @fragment
+ fn main1() {
+ _ = a;
+ ${
+ t.params.b_use === 'same'
+ ? ''
+ : `
+ }
+
+ @fragment
+ fn main2() {`
+ }
+ _ = b;
+ }`;
+
+ const collision =
+ t.params.a_group === t.params.b_group && t.params.a_binding === t.params.b_binding;
+ const shouldFail = collision && t.params.b_use === 'same';
+ t.expectCompileResult(!shouldFail, wgsl);
+ });
+
+g.test('binding_collision_unused_helper')
+ .desc('Test that binding points can collide in an unused helper function.')
+ .fn(t => {
+ const wgsl = `
+ @group(0) @binding(0) var<uniform> a : vec4f;
+ @group(0) @binding(0) var<uniform> b : vec4f;
+
+ fn foo() {
+ _ = a;
+ _ = b;
+ }`;
+
+ t.expectCompileResult(true, wgsl);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/access/vector.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/access/vector.spec.ts
index 0294fc2d56..766adc0fb8 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/access/vector.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/access/vector.spec.ts
@@ -178,10 +178,11 @@ g.test('vector')
.desc('Tests validation of vector indexed and swizzles')
.params(u =>
u
- .combine('case', keysOf(kCases)) //
.combine('vector_decl', ['const', 'let', 'var', 'param'] as const)
.combine('vector_width', [2, 3, 4] as const)
.combine('element_type', ['i32', 'u32', 'f32', 'f16', 'bool'] as const)
+ .beginSubcases()
+ .combine('case', keysOf(kCases))
)
.beforeAllSubcases(t => {
if (t.params.element_type === 'f16') {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/add_sub_mul.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/add_sub_mul.spec.ts
new file mode 100644
index 0000000000..3755eb7386
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/add_sub_mul.spec.ts
@@ -0,0 +1,320 @@
+export const description = `
+Validation tests for add/sub/mul expressions.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../common/util/data_tables.js';
+import { assert } from '../../../../../common/util/util.js';
+import { kBit } from '../../../../util/constants.js';
+import {
+ kAllScalarsAndVectors,
+ kConcreteNumericScalarsAndVectors,
+ kConvertableToFloatScalar,
+ ScalarType,
+ scalarTypeOf,
+ Type,
+ Value,
+ VectorType,
+} from '../../../../util/conversion.js';
+import { nextAfterF16, nextAfterF32 } from '../../../../util/math.js';
+import { reinterpretU16AsF16, reinterpretU32AsF32 } from '../../../../util/reinterpret.js';
+import { ShaderValidationTest } from '../../shader_validation_test.js';
+import {
+ kConstantAndOverrideStages,
+ validateConstOrOverrideBinaryOpEval,
+} from '../call/builtin/const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// A list of operators tested in this file.
+const kOperators = {
+ add: { op: '+' },
+ sub: { op: '-' },
+ mul: { op: '*' },
+};
+
+// A list of scalar and vector types.
+const kScalarAndVectorTypes = objectsToRecord(kAllScalarsAndVectors);
+const kConcreteNumericScalarAndVectorTypes = objectsToRecord(kConcreteNumericScalarsAndVectors);
+
+g.test('scalar_vector')
+ .desc(
+ `
+ Validates that scalar and vector expressions are only accepted for compatible numeric types.
+ `
+ )
+ .params(u =>
+ u
+ .combine('lhs', keysOf(kScalarAndVectorTypes))
+ .combine(
+ 'rhs',
+ // Skip vec3 and vec4 on the RHS to keep the number of subcases down.
+ // vec3 + vec3 and vec4 + vec4 is tested in execution tests.
+ keysOf(kScalarAndVectorTypes).filter(
+ value => !(value.startsWith('vec3') || value.startsWith('vec4'))
+ )
+ )
+ .beginSubcases()
+ .combine('op', keysOf(kOperators))
+ )
+ .beforeAllSubcases(t => {
+ if (
+ scalarTypeOf(kScalarAndVectorTypes[t.params.lhs]) === Type.f16 ||
+ scalarTypeOf(kScalarAndVectorTypes[t.params.rhs]) === Type.f16
+ ) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const op = kOperators[t.params.op];
+ const lhs = kScalarAndVectorTypes[t.params.lhs];
+ const rhs = kScalarAndVectorTypes[t.params.rhs];
+ const lhsElement = scalarTypeOf(lhs);
+ const rhsElement = scalarTypeOf(rhs);
+ const hasF16 = lhsElement === Type.f16 || rhsElement === Type.f16;
+ const code = `
+${hasF16 ? 'enable f16;' : ''}
+const lhs = ${lhs.create(0).wgsl()};
+const rhs = ${rhs.create(0).wgsl()};
+const foo = lhs ${op.op} rhs;
+`;
+
+ let elementsCompatible = lhsElement === rhsElement;
+ const elementTypes = [lhsElement, rhsElement];
+
+ // Booleans are not allowed for arithmetic expressions.
+ if (elementTypes.includes(Type.bool)) {
+ elementsCompatible = false;
+
+ // AbstractInt is allowed with everything but booleans which are already checked above.
+ } else if (elementTypes.includes(Type.abstractInt)) {
+ elementsCompatible = true;
+
+ // AbstractFloat is allowed with AbstractInt (checked above) or float types
+ } else if (elementTypes.includes(Type.abstractFloat)) {
+ elementsCompatible = elementTypes.every(e => kConvertableToFloatScalar.includes(e));
+ }
+
+ // Determine if the full type is compatible. The only invalid case is mixed vector sizes.
+ let valid = elementsCompatible;
+ if (lhs instanceof VectorType && rhs instanceof VectorType) {
+ valid = valid && lhs.width === rhs.width;
+ }
+
+ t.expectCompileResult(valid, code);
+ });
+
+g.test('scalar_vector_out_of_range')
+ .desc(
+ `
+ Checks that constant or override evaluation of add/sub/mul operations on scalar/vectors that produce out of range values cause validation errors.
+ - Checks for all concrete numeric scalar and vector types, including scalar * vector and vector * scalar.
+ - Checks for all vector elements that could cause the out of range to happen.
+ - Checks for pairs of values at one ULP difference at the boundary of the out of range.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op', keysOf(kOperators))
+ .combine('lhs', keysOf(kConcreteNumericScalarAndVectorTypes))
+ .expand('rhs', p => {
+ if (kScalarAndVectorTypes[p.lhs] instanceof VectorType) {
+ return [p.lhs, scalarTypeOf(kScalarAndVectorTypes[p.lhs]).toString()];
+ }
+ return [p.lhs];
+ })
+ .beginSubcases()
+ .expand('swap', p => {
+ if (p.lhs === p.rhs) {
+ return [false];
+ }
+ return [false, true];
+ })
+ .combine('nonZeroIndex', [0, 1, 2, 3])
+ .filter(p => {
+ const lType = kScalarAndVectorTypes[p.lhs];
+ if (lType instanceof VectorType) {
+ return lType.width > p.nonZeroIndex;
+ }
+ return p.nonZeroIndex === 0;
+ })
+ .combine('valueCase', ['halfmax', 'halfmax+ulp', 'sqrtmax', 'sqrtmax+ulp'] as const)
+ .combine('stage', kConstantAndOverrideStages)
+ )
+ .beforeAllSubcases(t => {
+ if (
+ scalarTypeOf(kScalarAndVectorTypes[t.params.lhs]) === Type.f16 ||
+ scalarTypeOf(kScalarAndVectorTypes[t.params.rhs]) === Type.f16
+ ) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const { op, valueCase, nonZeroIndex, swap } = t.params;
+ let { lhs, rhs } = t.params;
+
+ const elementType = scalarTypeOf(kScalarAndVectorTypes[lhs]);
+
+ // Handle the swapping of LHS and RHS to test all cases of scalar * vector.
+ if (swap) {
+ [rhs, lhs] = [lhs, rhs];
+ }
+
+ // What is the maximum representable value for the type? Also how do we add a ULP?
+ let maxValue = 0;
+ let nextAfter: (v: number) => number = v => v + 1;
+ let outOfRangeIsError = false;
+ switch (elementType) {
+ case Type.f16:
+ maxValue = reinterpretU16AsF16(kBit.f16.positive.max);
+ nextAfter = v => nextAfterF16(v, 'positive', 'no-flush');
+ outOfRangeIsError = true;
+ break;
+ case Type.f32:
+ maxValue = reinterpretU32AsF32(kBit.f32.positive.max);
+ nextAfter = v => nextAfterF32(v, 'positive', 'no-flush');
+ outOfRangeIsError = true;
+ break;
+ case Type.u32:
+ maxValue = kBit.u32.max;
+ break;
+ case Type.i32:
+ maxValue = kBit.i32.positive.max;
+ break;
+ }
+
+ // Decide on the test value that may or may not do an out of range computation.
+ let value;
+ switch (valueCase) {
+ case 'halfmax':
+ value = Math.floor(maxValue / 2);
+ break;
+ case 'halfmax+ulp':
+ value = nextAfter(Math.ceil(maxValue / 2));
+ break;
+ case 'sqrtmax':
+ value = Math.floor(Math.sqrt(maxValue));
+ break;
+ case 'sqrtmax+ulp':
+ value = nextAfter(Math.ceil(Math.sqrt(maxValue)));
+ break;
+ }
+
+ // What value will be computed by the test?
+ let computedValue;
+ let leftValue = value;
+ const rightValue = value;
+ switch (op) {
+ case 'add':
+ computedValue = value + value;
+ break;
+ case 'sub':
+ computedValue = -value - value;
+ leftValue = -value;
+ break;
+ case 'mul':
+ computedValue = value * value;
+ break;
+ }
+
+ // Creates either a scalar with the value, or a vector with the value only at a specific index.
+ const create = (type: ScalarType | VectorType, index: number, value: number): Value => {
+ if (type instanceof ScalarType) {
+ return type.create(value);
+ } else {
+ assert(type instanceof VectorType);
+ const values = new Array(type.width);
+ values.fill(0);
+ values[index] = value;
+ return type.create(values);
+ }
+ };
+
+ // Check if there is overflow
+ const success = Math.abs(computedValue) <= maxValue || !outOfRangeIsError;
+ validateConstOrOverrideBinaryOpEval(
+ t,
+ kOperators[op].op,
+ success,
+ t.params.stage,
+ create(kScalarAndVectorTypes[lhs], nonZeroIndex, leftValue),
+ t.params.stage,
+ create(kScalarAndVectorTypes[rhs], nonZeroIndex, rightValue)
+ );
+ });
+
+interface InvalidTypeConfig {
+ // An expression that produces a value of the target type.
+ expr: string;
+ // A function that converts an expression of the target type into a valid integer operand.
+ control: (x: string) => string;
+}
+const kInvalidTypes: Record<string, InvalidTypeConfig> = {
+ array: {
+ expr: 'arr',
+ control: e => `${e}[0]`,
+ },
+
+ ptr: {
+ expr: '(&u)',
+ control: e => `*${e}`,
+ },
+
+ atomic: {
+ expr: 'a',
+ control: e => `atomicLoad(&${e})`,
+ },
+
+ texture: {
+ expr: 't',
+ control: e => `i32(textureLoad(${e}, vec2(), 0).x)`,
+ },
+
+ sampler: {
+ expr: 's',
+ control: e => `i32(textureSampleLevel(t, ${e}, vec2(), 0).x)`,
+ },
+
+ struct: {
+ expr: 'str',
+ control: e => `${e}.u`,
+ },
+};
+
+g.test('invalid_type_with_itself')
+ .desc(
+ `
+ Validates that expressions are never accepted for non-scalar, non-vector, and non-matrix types.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op', keysOf(kOperators))
+ .combine('type', keysOf(kInvalidTypes))
+ .combine('control', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ const op = kOperators[t.params.op];
+ const type = kInvalidTypes[t.params.type];
+ const expr = t.params.control ? type.control(type.expr) : type.expr;
+ const code = `
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+@group(0) @binding(2) var<storage, read_write> a : atomic<i32>;
+
+struct S { u : u32 }
+
+var<private> u : u32;
+var<private> m : mat2x2f;
+var<private> arr : array<i32, 4>;
+var<private> str : S;
+
+@compute @workgroup_size(1)
+fn main() {
+ let foo = ${expr} ${op.op} ${expr};
+}
+`;
+
+ t.expectCompileResult(t.params.control, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/and_or_xor.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/and_or_xor.spec.ts
new file mode 100644
index 0000000000..3dd3607365
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/and_or_xor.spec.ts
@@ -0,0 +1,182 @@
+export const description = `
+Validation tests for logical and bitwise and/or/xor expressions.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../common/util/data_tables.js';
+import {
+ kAllScalarsAndVectors,
+ ScalarType,
+ scalarTypeOf,
+ Type,
+ VectorType,
+} from '../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// A list of operators and a flag for whether they support boolean values or not.
+const kOperators = {
+ and: { op: '&', supportsBool: true },
+ or: { op: '|', supportsBool: true },
+ xor: { op: '^', supportsBool: false },
+};
+
+// A list of scalar and vector types.
+const kScalarAndVectorTypes = objectsToRecord(kAllScalarsAndVectors);
+
+g.test('scalar_vector')
+ .desc(
+ `
+ Validates that scalar and vector expressions are only accepted for bool or compatible integer types.
+ `
+ )
+ .params(u =>
+ u
+ .combine('lhs', keysOf(kScalarAndVectorTypes))
+ .combine(
+ 'rhs',
+ // Skip vec3 and vec4 on the RHS to keep the number of subcases down.
+ // vec3 + vec3 and vec4 + vec4 is tested in execution tests.
+ keysOf(kScalarAndVectorTypes).filter(
+ value => !(value.startsWith('vec3') || value.startsWith('vec4'))
+ )
+ )
+ .beginSubcases()
+ .combine('op', keysOf(kOperators))
+ )
+ .beforeAllSubcases(t => {
+ if (
+ scalarTypeOf(kScalarAndVectorTypes[t.params.lhs]) === Type.f16 ||
+ scalarTypeOf(kScalarAndVectorTypes[t.params.rhs]) === Type.f16
+ ) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const op = kOperators[t.params.op];
+ const lhs = kScalarAndVectorTypes[t.params.lhs];
+ const rhs = kScalarAndVectorTypes[t.params.rhs];
+ const lhsElement = scalarTypeOf(lhs);
+ const rhsElement = scalarTypeOf(rhs);
+ const hasF16 = lhsElement === Type.f16 || rhsElement === Type.f16;
+ const code = `
+${hasF16 ? 'enable f16;' : ''}
+const lhs = ${lhs.create(0).wgsl()};
+const rhs = ${rhs.create(0).wgsl()};
+const foo = lhs ${op.op} rhs;
+`;
+
+ // Determine if the element types are compatible.
+ const kIntegerTypes = [Type.abstractInt, Type.i32, Type.u32];
+ let elementIsCompatible = false;
+ if (lhsElement === Type.abstractInt) {
+ // Abstract integers are compatible with any other integer type.
+ elementIsCompatible = kIntegerTypes.includes(rhsElement);
+ } else if (rhsElement === Type.abstractInt) {
+ // Abstract integers are compatible with any other numeric type.
+ elementIsCompatible = kIntegerTypes.includes(lhsElement);
+ } else if (kIntegerTypes.includes(lhsElement)) {
+ // Concrete integers are only compatible with values with the exact same type.
+ elementIsCompatible = lhsElement === rhsElement;
+ } else if (lhsElement === Type.bool) {
+ // Booleans are only compatible with other booleans.
+ elementIsCompatible = rhsElement === Type.bool;
+ }
+
+ // Determine if the full type is compatible.
+ let valid = false;
+ if (lhs instanceof ScalarType && rhs instanceof ScalarType) {
+ valid = elementIsCompatible;
+ } else if (lhs instanceof VectorType && rhs instanceof VectorType) {
+ // Vectors are only compatible with if the vector widths match.
+ valid = lhs.width === rhs.width && elementIsCompatible;
+ }
+
+ if (lhsElement === Type.bool) {
+ valid &&= op.supportsBool;
+ }
+
+ t.expectCompileResult(valid, code);
+ });
+
+interface InvalidTypeConfig {
+ // An expression that produces a value of the target type.
+ expr: string;
+ // A function that converts an expression of the target type into a valid integer operand.
+ control: (x: string) => string;
+}
+const kInvalidTypes: Record<string, InvalidTypeConfig> = {
+ mat2x2f: {
+ expr: 'm',
+ control: e => `i32(${e}[0][0])`,
+ },
+
+ array: {
+ expr: 'arr',
+ control: e => `${e}[0]`,
+ },
+
+ ptr: {
+ expr: '(&u)',
+ control: e => `*${e}`,
+ },
+
+ atomic: {
+ expr: 'a',
+ control: e => `atomicLoad(&${e})`,
+ },
+
+ texture: {
+ expr: 't',
+ control: e => `i32(textureLoad(${e}, vec2(), 0).x)`,
+ },
+
+ sampler: {
+ expr: 's',
+ control: e => `i32(textureSampleLevel(t, ${e}, vec2(), 0).x)`,
+ },
+
+ struct: {
+ expr: 'str',
+ control: e => `${e}.u`,
+ },
+};
+
+g.test('invalid_types')
+ .desc(
+ `
+ Validates that expressions are never accepted for non-scalar and non-vector types.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op', keysOf(kOperators))
+ .combine('type', keysOf(kInvalidTypes))
+ .combine('control', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ const op = kOperators[t.params.op];
+ const type = kInvalidTypes[t.params.type];
+ const expr = t.params.control ? type.control(type.expr) : type.expr;
+ const code = `
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+@group(0) @binding(2) var<storage, read_write> a : atomic<i32>;
+
+struct S { u : u32 }
+
+var<private> u : u32;
+var<private> m : mat2x2f;
+var<private> arr : array<i32, 4>;
+var<private> str : S;
+
+@compute @workgroup_size(1)
+fn main() {
+ let foo = ${expr} ${op.op} ${expr};
+}
+`;
+
+ t.expectCompileResult(t.params.control, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/bitwise_shift.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/bitwise_shift.spec.ts
index 5f7b995ded..e3f13d6813 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/bitwise_shift.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/bitwise_shift.spec.ts
@@ -3,6 +3,13 @@ Validation tests for the bitwise shift binary expression operations
`;
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ numElementsOf,
+ scalarTypeOf,
+} from '../../../../util/conversion.js';
import { ShaderValidationTest } from '../../shader_validation_test.js';
export const g = makeTestGroup(ShaderValidationTest);
@@ -21,6 +28,137 @@ function vectorize(v: string, size: number | undefined): string {
return v;
}
+// A list of scalar and vector types.
+const kScalarAndVectorTypes = objectsToRecord(kAllScalarsAndVectors);
+
+g.test('scalar_vector')
+ .desc(
+ `
+ Validates that scalar and vector expressions are only accepted when the LHS is an integer and the RHS is abstract or unsigned.
+ `
+ )
+ .params(u =>
+ u
+ .combine('lhs', keysOf(kScalarAndVectorTypes))
+ .combine(
+ 'rhs',
+ // Skip vec3 and vec4 on the RHS to keep the number of subcases down.
+ keysOf(kScalarAndVectorTypes).filter(
+ value => !(value.startsWith('vec3') || value.startsWith('vec4'))
+ )
+ )
+ .beginSubcases()
+ .combine('op', ['<<', '>>'])
+ )
+ .beforeAllSubcases(t => {
+ if (
+ scalarTypeOf(kScalarAndVectorTypes[t.params.lhs]) === Type.f16 ||
+ scalarTypeOf(kScalarAndVectorTypes[t.params.rhs]) === Type.f16
+ ) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const lhs = kScalarAndVectorTypes[t.params.lhs];
+ const rhs = kScalarAndVectorTypes[t.params.rhs];
+ const lhsElement = scalarTypeOf(lhs);
+ const rhsElement = scalarTypeOf(rhs);
+ const hasF16 = lhsElement === Type.f16 || rhsElement === Type.f16;
+ const code = `
+${hasF16 ? 'enable f16;' : ''}
+const lhs = ${lhs.create(0).wgsl()};
+const rhs = ${rhs.create(0).wgsl()};
+const foo = lhs ${t.params.op} rhs;
+`;
+
+ // The LHS must be an integer, and the RHS must be an abstract/unsigned integer.
+ // The vector widths must also match.
+ const lhs_valid = [Type.abstractInt, Type.i32, Type.u32].includes(lhsElement);
+ const rhs_valid = [Type.abstractInt, Type.u32].includes(rhsElement);
+ const valid = lhs_valid && rhs_valid && numElementsOf(lhs) === numElementsOf(rhs);
+ t.expectCompileResult(valid, code);
+ });
+
+interface InvalidTypeConfig {
+ // An expression that produces a value of the target type.
+ expr: string;
+ // A function that converts an expression of the target type into a valid u32 operand.
+ control: (x: string) => string;
+}
+const kInvalidTypes: Record<string, InvalidTypeConfig> = {
+ mat2x2f: {
+ expr: 'm',
+ control: e => `u32(${e}[0][0])`,
+ },
+
+ array: {
+ expr: 'arr',
+ control: e => `${e}[0]`,
+ },
+
+ ptr: {
+ expr: '(&u)',
+ control: e => `*${e}`,
+ },
+
+ atomic: {
+ expr: 'a',
+ control: e => `atomicLoad(&${e})`,
+ },
+
+ texture: {
+ expr: 't',
+ control: e => `u32(textureLoad(${e}, vec2(), 0).x)`,
+ },
+
+ sampler: {
+ expr: 's',
+ control: e => `u32(textureSampleLevel(t, ${e}, vec2(), 0).x)`,
+ },
+
+ struct: {
+ expr: 'str',
+ control: e => `${e}.u`,
+ },
+};
+
+g.test('invalid_types')
+ .desc(
+ `
+ Validates that expressions are never accepted for non-scalar and non-vector types.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op', ['<<', '>>'])
+ .combine('type', keysOf(kInvalidTypes))
+ .combine('control', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ const type = kInvalidTypes[t.params.type];
+ const expr = t.params.control ? type.control(type.expr) : type.expr;
+ const code = `
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+@group(0) @binding(2) var<storage, read_write> a : atomic<u32>;
+
+struct S { u : u32 }
+
+var<private> u : u32;
+var<private> m : mat2x2f;
+var<private> arr : array<u32, 4>;
+var<private> str : S;
+
+@compute @workgroup_size(1)
+fn main() {
+ let foo = ${expr} ${t.params.op} ${expr};
+}
+`;
+
+ t.expectCompileResult(t.params.control, code);
+ });
+
const kLeftShiftCases = [
// rhs >= bitwidth fails
{ lhs: `0u`, rhs: `31u`, pass: true },
@@ -80,28 +218,6 @@ fn main() {
t.expectCompileResult(t.params.case.pass, code);
});
-g.test('shift_left_vec_size_mismatch')
- .desc('Tests validation of binary left shift of vectors with mismatched sizes')
- .params(u =>
- u
- .combine('vectorize_lhs', [2, 3, 4]) //
- .combine('vectorize_rhs', [2, 3, 4])
- )
- .fn(t => {
- const lhs = `1`;
- const rhs = `1`;
- const lhs_vec_size = t.params.vectorize_lhs;
- const rhs_vec_size = t.params.vectorize_rhs;
- const code = `
-@compute @workgroup_size(1)
-fn main() {
- const r = ${vectorize(lhs, lhs_vec_size)} << ${vectorize(rhs, rhs_vec_size)};
-}
- `;
- const pass = lhs_vec_size === rhs_vec_size;
- t.expectCompileResult(pass, code);
- });
-
const kRightShiftCases = [
// rhs >= bitwidth fails
{ lhs: `0u`, rhs: `31u`, pass: true },
@@ -142,25 +258,3 @@ fn main() {
`;
t.expectCompileResult(t.params.case.pass, code);
});
-
-g.test('shift_right_vec_size_mismatch')
- .desc('Tests validation of binary right shift of vectors with mismatched sizes')
- .params(u =>
- u
- .combine('vectorize_lhs', [2, 3, 4]) //
- .combine('vectorize_rhs', [2, 3, 4])
- )
- .fn(t => {
- const lhs = `1`;
- const rhs = `1`;
- const lhs_vec_size = t.params.vectorize_lhs;
- const rhs_vec_size = t.params.vectorize_rhs;
- const code = `
-@compute @workgroup_size(1)
-fn main() {
- const r = ${vectorize(lhs, lhs_vec_size)} >> ${vectorize(rhs, rhs_vec_size)};
-}
- `;
- const pass = lhs_vec_size === rhs_vec_size;
- t.expectCompileResult(pass, code);
- });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/comparison.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/comparison.spec.ts
new file mode 100644
index 0000000000..6115874a10
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/comparison.spec.ts
@@ -0,0 +1,186 @@
+export const description = `
+Validation tests for comparison expressions.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../common/util/data_tables.js';
+import {
+ isFloatType,
+ kAllScalarsAndVectors,
+ ScalarType,
+ scalarTypeOf,
+ Type,
+ VectorType,
+} from '../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// A list of scalar and vector types.
+const kScalarAndVectorTypes = objectsToRecord(kAllScalarsAndVectors);
+
+// A list of comparison operators and a flag for whether they support boolean values or not.
+const kComparisonOperators = {
+ eq: { op: '==', supportsBool: true },
+ ne: { op: '!=', supportsBool: true },
+ gt: { op: '>', supportsBool: false },
+ ge: { op: '>=', supportsBool: false },
+ lt: { op: '<', supportsBool: false },
+ le: { op: '<=', supportsBool: false },
+};
+
+g.test('scalar_vector')
+ .desc(
+ `
+ Validates that scalar and vector comparison expressions are only accepted for compatible types.
+ `
+ )
+ .params(u =>
+ u
+ .combine('lhs', keysOf(kScalarAndVectorTypes))
+ .combine(
+ 'rhs',
+ // Skip vec3 and vec4 on the RHS to keep the number of subcases down.
+ keysOf(kScalarAndVectorTypes).filter(
+ value => !(value.startsWith('vec3') || value.startsWith('vec4'))
+ )
+ )
+ .beginSubcases()
+ .combine('op', keysOf(kComparisonOperators))
+ )
+ .beforeAllSubcases(t => {
+ if (
+ scalarTypeOf(kScalarAndVectorTypes[t.params.lhs]) === Type.f16 ||
+ scalarTypeOf(kScalarAndVectorTypes[t.params.rhs]) === Type.f16
+ ) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const lhs = kScalarAndVectorTypes[t.params.lhs];
+ const rhs = kScalarAndVectorTypes[t.params.rhs];
+ const lhsElement = scalarTypeOf(lhs);
+ const rhsElement = scalarTypeOf(rhs);
+ const hasF16 = lhsElement === Type.f16 || rhsElement === Type.f16;
+ const code = `
+${hasF16 ? 'enable f16;' : ''}
+const lhs = ${lhs.create(0).wgsl()};
+const rhs = ${rhs.create(0).wgsl()};
+const foo = lhs ${kComparisonOperators[t.params.op].op} rhs;
+`;
+
+ let valid = false;
+
+ // Determine if the element types are comparable.
+ let elementIsCompatible = false;
+ if (lhsElement === Type.abstractInt) {
+ // Abstract integers are comparable to any other numeric type.
+ elementIsCompatible = rhsElement !== Type.bool;
+ } else if (rhsElement === Type.abstractInt) {
+ // Abstract integers are comparable to any other numeric type.
+ elementIsCompatible = lhsElement !== Type.bool;
+ } else if (lhsElement === Type.abstractFloat) {
+ // Abstract floats are comparable to any other float type.
+ elementIsCompatible = isFloatType(rhsElement);
+ } else if (rhsElement === Type.abstractFloat) {
+ // Abstract floats are comparable to any other float type.
+ elementIsCompatible = isFloatType(lhsElement);
+ } else {
+ // Non-abstract types are only comparable to values with the exact same type.
+ elementIsCompatible = lhsElement === rhsElement;
+ }
+
+ // Determine if the full type is comparable.
+ if (lhs instanceof ScalarType && rhs instanceof ScalarType) {
+ valid = elementIsCompatible;
+ } else if (lhs instanceof VectorType && rhs instanceof VectorType) {
+ // Vectors are only comparable if the vector widths match.
+ valid = lhs.width === rhs.width && elementIsCompatible;
+ }
+
+ if (lhsElement === Type.bool) {
+ valid &&= kComparisonOperators[t.params.op].supportsBool;
+ }
+
+ t.expectCompileResult(valid, code);
+ });
+
+interface InvalidTypeConfig {
+ // An expression that produces a value of the target type.
+ expr: string;
+ // A function that converts an expression of the target type into a valid comparison operand.
+ control: (x: string) => string;
+}
+const kInvalidTypes: Record<string, InvalidTypeConfig> = {
+ mat2x2f: {
+ expr: 'm',
+ control: e => `${e}[0]`,
+ },
+
+ array: {
+ expr: 'arr',
+ control: e => `${e}[0]`,
+ },
+
+ ptr: {
+ expr: '(&u)',
+ control: e => `*${e}`,
+ },
+
+ atomic: {
+ expr: 'a',
+ control: e => `atomicLoad(&${e})`,
+ },
+
+ texture: {
+ expr: 't',
+ control: e => `textureLoad(${e}, vec2(), 0)`,
+ },
+
+ sampler: {
+ expr: 's',
+ control: e => `textureSampleLevel(t, ${e}, vec2(), 0)`,
+ },
+
+ struct: {
+ expr: 'str',
+ control: e => `${e}.u`,
+ },
+};
+
+g.test('invalid_types')
+ .desc(
+ `
+ Validates that comparison expressions are never accepted for non-scalar and non-vector types.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op', keysOf(kComparisonOperators))
+ .combine('type', keysOf(kInvalidTypes))
+ .combine('control', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ const type = kInvalidTypes[t.params.type];
+ const expr = t.params.control ? type.control(type.expr) : type.expr;
+ const code = `
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+@group(0) @binding(2) var<storage, read_write> a : atomic<i32>;
+
+struct S { u : u32 }
+
+var<private> u : u32;
+var<private> m : mat2x2f;
+var<private> arr : array<i32, 4>;
+var<private> str : S;
+
+@compute @workgroup_size(1)
+fn main() {
+ let foo = ${expr} ${kComparisonOperators[t.params.op].op} ${expr};
+}
+`;
+
+ t.expectCompileResult(t.params.control, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/div_rem.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/div_rem.spec.ts
new file mode 100644
index 0000000000..0f07abe3ae
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/binary/div_rem.spec.ts
@@ -0,0 +1,279 @@
+export const description = `
+Validation tests for division and remainder expressions.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../common/util/data_tables.js';
+import { assert } from '../../../../../common/util/util.js';
+import { kBit } from '../../../../util/constants.js';
+import {
+ ScalarType,
+ Type,
+ Value,
+ VectorType,
+ kAllScalarsAndVectors,
+ kConcreteNumericScalarsAndVectors,
+ kConvertableToFloatScalar,
+ scalarTypeOf,
+} from '../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../shader_validation_test.js';
+import {
+ kConstantAndOverrideStages,
+ validateConstOrOverrideBinaryOpEval,
+} from '../call/builtin/const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// A list of operators tested in this file.
+const kOperators = {
+ div: { op: '/' },
+ rem: { op: '%' },
+} as const;
+
+// A list of scalar and vector types.
+const kScalarAndVectorTypes = objectsToRecord(kAllScalarsAndVectors);
+const kConcreteNumericScalarAndVectorTypes = objectsToRecord(kConcreteNumericScalarsAndVectors);
+
+g.test('scalar_vector')
+ .desc(
+ `
+ Validates that scalar and vector expressions are only accepted for compatible numeric types.
+ `
+ )
+ .params(u =>
+ u
+ .combine('lhs', keysOf(kScalarAndVectorTypes))
+ .combine(
+ 'rhs',
+ // Skip vec3 and vec4 on the RHS to keep the number of subcases down.
+ // vec3 + vec3 and vec4 + vec4 is tested in execution tests.
+ keysOf(kScalarAndVectorTypes).filter(
+ value => !(value.startsWith('vec3') || value.startsWith('vec4'))
+ )
+ )
+ .beginSubcases()
+ .combine('op', keysOf(kOperators))
+ )
+ .beforeAllSubcases(t => {
+ if (
+ scalarTypeOf(kScalarAndVectorTypes[t.params.lhs]) === Type.f16 ||
+ scalarTypeOf(kScalarAndVectorTypes[t.params.rhs]) === Type.f16
+ ) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const op = kOperators[t.params.op];
+ const lhs = kScalarAndVectorTypes[t.params.lhs];
+ const rhs = kScalarAndVectorTypes[t.params.rhs];
+ const lhsElement = scalarTypeOf(lhs);
+ const rhsElement = scalarTypeOf(rhs);
+ const hasF16 = lhsElement === Type.f16 || rhsElement === Type.f16;
+ const code = `
+${hasF16 ? 'enable f16;' : ''}
+const lhs = ${lhs.create(1).wgsl()};
+const rhs = ${rhs.create(1).wgsl()};
+const foo = lhs ${op.op} rhs;
+`;
+
+ let elementsCompatible = lhsElement === rhsElement;
+ const elementTypes = [lhsElement, rhsElement];
+
+ // Booleans are not allowed for arithmetic expressions.
+ if (elementTypes.includes(Type.bool)) {
+ elementsCompatible = false;
+
+ // AbstractInt is allowed with everything but booleans which are already checked above.
+ } else if (elementTypes.includes(Type.abstractInt)) {
+ elementsCompatible = true;
+
+ // AbstractFloat is allowed with AbstractInt (checked above) or float types
+ } else if (elementTypes.includes(Type.abstractFloat)) {
+ elementsCompatible = elementTypes.every(e => kConvertableToFloatScalar.includes(e));
+ }
+
+ // Determine if the full type is compatible. The only invalid case is mixed vector sizes.
+ let valid = elementsCompatible;
+ if (lhs instanceof VectorType && rhs instanceof VectorType) {
+ valid = valid && lhs.width === rhs.width;
+ }
+
+ t.expectCompileResult(valid, code);
+ });
+
+g.test('scalar_vector_out_of_range')
+ .desc(
+ `
+ Checks that constant or override evaluation of div/rem operations on scalar/vectors that produce out of division by 0 or out of range values cause validation errors.
+ - Checks for all concrete numeric scalar and vector types, including scalar * vector and vector * scalar.
+ - Checks for all vector elements that could cause the out of range to happen.
+ - Checks for valid small cases and 0, also the minimum i32.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op', keysOf(kOperators))
+ .combine('lhs', keysOf(kConcreteNumericScalarAndVectorTypes))
+ .expand('rhs', p => {
+ if (kScalarAndVectorTypes[p.lhs] instanceof VectorType) {
+ return [p.lhs, scalarTypeOf(kScalarAndVectorTypes[p.lhs]).toString()];
+ }
+ return [p.lhs];
+ })
+ .beginSubcases()
+ .expand('swap', p => {
+ if (p.lhs === p.rhs) {
+ return [false];
+ }
+ return [false, true];
+ })
+ .combine('nonOneIndex', [0, 1, 2, 3])
+ .filter(p => {
+ const lType = kScalarAndVectorTypes[p.lhs];
+ if (lType instanceof VectorType) {
+ return lType.width > p.nonOneIndex;
+ }
+ return p.nonOneIndex === 0;
+ })
+ .expandWithParams(p => {
+ const cases = [
+ { leftValue: 42, rightValue: 0, error: true, leftRuntime: false },
+ { leftValue: 42, rightValue: 0, error: true, leftRuntime: true },
+ { leftValue: 0, rightValue: 0, error: true, leftRuntime: true },
+ { leftValue: 0, rightValue: 42, error: false, leftRuntime: false },
+ ];
+ if (p.lhs === 'i32') {
+ cases.push({
+ leftValue: -kBit.i32.negative.min,
+ rightValue: -1,
+ error: true,
+ leftRuntime: false,
+ });
+ cases.push({
+ leftValue: -kBit.i32.negative.min + 1,
+ rightValue: -1,
+ error: false,
+ leftRuntime: false,
+ });
+ }
+ return cases;
+ })
+ .combine('stage', kConstantAndOverrideStages)
+ )
+ .beforeAllSubcases(t => {
+ if (
+ scalarTypeOf(kScalarAndVectorTypes[t.params.lhs]) === Type.f16 ||
+ scalarTypeOf(kScalarAndVectorTypes[t.params.rhs]) === Type.f16
+ ) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const { op, leftValue, rightValue, error, leftRuntime, nonOneIndex, swap } = t.params;
+ let { lhs, rhs } = t.params;
+
+ // Handle the swapping of LHS and RHS to test all cases of scalar * vector.
+ if (swap) {
+ [rhs, lhs] = [lhs, rhs];
+ }
+
+ // Creates either a scalar with the value, or a vector with the value only at a specific index.
+ const create = (type: ScalarType | VectorType, index: number, value: number): Value => {
+ if (type instanceof ScalarType) {
+ return type.create(value);
+ } else {
+ assert(type instanceof VectorType);
+ const values = new Array(type.width);
+ values.fill(1);
+ values[index] = value;
+ return type.create(values);
+ }
+ };
+
+ // Check if there is overflow
+ validateConstOrOverrideBinaryOpEval(
+ t,
+ kOperators[op].op,
+ !error,
+ leftRuntime ? 'runtime' : t.params.stage,
+ create(kScalarAndVectorTypes[lhs], nonOneIndex, leftValue),
+ t.params.stage,
+ create(kScalarAndVectorTypes[rhs], nonOneIndex, rightValue)
+ );
+ });
+
+interface InvalidTypeConfig {
+ // An expression that produces a value of the target type.
+ expr: string;
+ // A function that converts an expression of the target type into a valid integer operand.
+ control: (x: string) => string;
+}
+const kInvalidTypes: Record<string, InvalidTypeConfig> = {
+ array: {
+ expr: 'arr',
+ control: e => `${e}[0]`,
+ },
+
+ ptr: {
+ expr: '(&u)',
+ control: e => `*${e}`,
+ },
+
+ atomic: {
+ expr: 'a',
+ control: e => `atomicLoad(&${e})`,
+ },
+
+ texture: {
+ expr: 't',
+ control: e => `i32(textureLoad(${e}, vec2(), 0).x)`,
+ },
+
+ sampler: {
+ expr: 's',
+ control: e => `i32(textureSampleLevel(t, ${e}, vec2(), 0).x)`,
+ },
+
+ struct: {
+ expr: 'str',
+ control: e => `${e}.u`,
+ },
+};
+
+g.test('invalid_type_with_itself')
+ .desc(
+ `
+ Validates that expressions are never accepted for non-scalar, non-vector, and non-matrix types.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op', keysOf(kOperators))
+ .combine('type', keysOf(kInvalidTypes))
+ .combine('control', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ const op = kOperators[t.params.op];
+ const type = kInvalidTypes[t.params.type];
+ const expr = t.params.control ? type.control(type.expr) : type.expr;
+ const code = `
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+@group(0) @binding(2) var<storage, read_write> a : atomic<i32>;
+
+struct S { u : u32 }
+
+var<private> u : u32;
+var<private> m : mat2x2f;
+var<private> arr : array<i32, 4>;
+var<private> str : S;
+
+@compute @workgroup_size(1)
+fn main() {
+ let foo = ${expr} ${op.op} ${expr};
+}
+`;
+
+ t.expectCompileResult(t.params.control, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/abs.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/abs.spec.ts
index 32ecb0cbc8..2b0375d783 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/abs.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/abs.spec.ts
@@ -6,9 +6,9 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- elementType,
- kAllFloatAndIntegerScalarsAndVectors,
+ Type,
+ kAllNumericScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -21,7 +21,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatAndIntegerScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kAllNumericScalarsAndVectors);
g.test('values')
.desc(
@@ -38,7 +38,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() never
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -52,3 +52,107 @@ Validates that constant evaluation and override evaluation of ${builtin}() never
t.params.stage
);
});
+
+const kTests = {
+ valid: {
+ src: `_ = abs(1);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = abs(i32_alias(1));`,
+ pass: true,
+ },
+
+ bool: {
+ src: `_ = abs(false);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = abs(vec2<bool>(false, true));`,
+ pass: false,
+ },
+ matrix: {
+ src: `_ = abs(mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = abs(a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<u32, 5>;
+ _ = abs(a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = abs(k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = abs(a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = abs(read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = 1u;
+ let p: ptr<function, u32> = &a;
+ _ = abs(p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = 1u;
+ let p: ptr<function, u32> = &a;
+ _ = abs(*p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = abs(s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = abs(t);`,
+ pass: false,
+ },
+ no_params: {
+ src: `_ = abs();`,
+ pass: false,
+ },
+ too_many_params: {
+ src: `_ = abs(1, 2);`,
+ pass: false,
+ },
+};
+
+g.test('parameters')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const code = `
+alias i32_alias = i32;
+
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: texture_2d<f32>;
+
+var<workgroup> a: atomic<u32>;
+
+struct A {
+ i: u32,
+}
+struct B {
+ arry: array<u32>,
+}
+@group(0) @binding(3) var<storage> k: B;
+
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acos.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acos.spec.ts
index 82171ed4b1..d76bb470b0 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acos.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acos.spec.ts
@@ -6,18 +6,18 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
+import { absBigInt } from '../../../../../util/math.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinusTwoToTwo,
+ minusTwoToTwoRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -25,7 +25,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -39,15 +39,23 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(kMinusTwoToTwo, fullRangeForType(kValuesTypes[u.type])))
+ .expand('value', u =>
+ unique(
+ minusTwoToTwoRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
- const expectedResult = Math.abs(t.params.value) <= 1;
+ const expectedResult =
+ typeof t.params.value === 'bigint'
+ ? absBigInt(t.params.value) <= 1n
+ : Math.abs(t.params.value) <= 1;
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -57,7 +65,8 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+// f32 is included here to confirm that validation is failing due to a type issue and not something else.
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -71,8 +80,137 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(0)],
'constant'
);
});
+
+const kTests = {
+ valid: {
+ src: `_ = acos(1);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = acos(f32_alias(1));`,
+ pass: true,
+ },
+
+ bool: {
+ src: `_ = acos(false);`,
+ pass: false,
+ },
+ i32: {
+ src: `_ = acos(1i);`,
+ pass: false,
+ },
+ u32: {
+ src: `_ = acos(1u);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = acos(vec2<bool>(false, true));`,
+ pass: false,
+ },
+ vec_i32: {
+ src: `_ = acos(vec2<i32>(1, 1));`,
+ pass: false,
+ },
+ vec_u32: {
+ src: `_ = acos(vec2<u32>(1, 1));`,
+ pass: false,
+ },
+ matrix: {
+ src: `_ = acos(mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = acos(a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<u32, 5>;
+ _ = acos(a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = acos(k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = acos(a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = acos(read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = acos(p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = acos(*p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = acos(s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = acos(t);`,
+ pass: false,
+ },
+ no_params: {
+ src: `_ = acos();`,
+ pass: false,
+ },
+ too_many_params: {
+ src: `_ = acos(1, 2);`,
+ pass: false,
+ },
+
+ greater_then_one: {
+ src: `_ = acos(1.1f);`,
+ pass: false,
+ },
+ less_then_negative_one: {
+ src: `_ = acos(-1.1f);`,
+ pass: false,
+ },
+};
+
+g.test('parameters')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const code = `
+alias f32_alias = f32;
+
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: texture_2d<f32>;
+
+var<workgroup> a: atomic<u32>;
+
+struct A {
+ i: u32,
+}
+struct B {
+ arry: array<u32>,
+}
+@group(0) @binding(3) var<storage> k: B;
+
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acosh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acosh.spec.ts
index a7ab8d83f9..82da85fdde 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acosh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/acosh.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -18,7 +17,7 @@ import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinusTwoToTwo,
+ minusTwoToTwoRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -26,7 +25,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,16 +39,25 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(fullRangeForType(kValuesTypes[u.type]), kMinusTwoToTwo))
+ .expand('value', u =>
+ unique(
+ minusTwoToTwoRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
- const expectedResult = isRepresentable(Math.acosh(t.params.value), elementType(type));
+ const expectedResult = isRepresentable(
+ Math.acosh(Number(t.params.value)),
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -59,7 +67,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord(kConcreteIntegerScalarsAndVectors);
g.test('integer_argument')
.desc(
@@ -73,8 +81,132 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
- [type.create(1)],
+ /* expectedResult */ type === Type.f32,
+ [type.create(type === Type.abstractInt ? 1n : 1)],
'constant'
);
});
+
+const kTests = {
+ valid: {
+ src: `_ = acosh(1);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = acosh(f32_alias(1));`,
+ pass: true,
+ },
+
+ bool: {
+ src: `_ = acosh(false);`,
+ pass: false,
+ },
+ i32: {
+ src: `_ = acosh(1i);`,
+ pass: false,
+ },
+ u32: {
+ src: `_ = acosh(1u);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = acosh(vec2<bool>(false, true));`,
+ pass: false,
+ },
+ vec_i32: {
+ src: `_ = acosh(vec2<i32>(1, 1));`,
+ pass: false,
+ },
+ vec_u32: {
+ src: `_ = acosh(vec2<u32>(1, 1));`,
+ pass: false,
+ },
+ matrix: {
+ src: `_ = acosh(mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = acosh(a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<u32, 5>;
+ _ = acosh(a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = acosh(k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = acosh(a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = acosh(read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = acosh(p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = acosh(*p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = acosh(s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = acosh(t);`,
+ pass: false,
+ },
+ no_params: {
+ src: `_ = acosh();`,
+ pass: false,
+ },
+ too_many_params: {
+ src: `_ = acosh(1, 2);`,
+ pass: false,
+ },
+
+ less_then_one: {
+ src: `_ = acosh(.9f);`,
+ pass: false,
+ },
+};
+
+g.test('parameters')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const code = `
+alias f32_alias = f32;
+
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: texture_2d<f32>;
+
+var<workgroup> a: atomic<u32>;
+
+struct A {
+ i: u32,
+}
+struct B {
+ arry: array<u32>,
+}
+@group(0) @binding(3) var<storage> k: B;
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/all.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/all.spec.ts
new file mode 100644
index 0000000000..f52d24e0cc
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/all.spec.ts
@@ -0,0 +1,191 @@
+const builtin = 'all';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import { Type, elementTypeOf, kAllScalarsAndVectors } from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import { validateConstOrOverrideBuiltinEval } from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kArgumentTypes = objectsToRecord(kAllScalarsAndVectors);
+
+g.test('argument_types')
+ .desc(
+ `
+Validates that scalar and vector arguments are rejected by ${builtin}() if not bool or vecN<bool>
+`
+ )
+ .params(u => u.combine('type', keysOf(kArgumentTypes)))
+ .fn(t => {
+ const type = kArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ elementTypeOf(type) === Type.bool,
+ [type.create(0)],
+ 'constant',
+ /* returnType */ Type.bool
+ );
+ });
+
+const kTests = {
+ valid: {
+ src: `_ = ${builtin}(true);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = ${builtin}(bool_alias(true));`,
+ pass: true,
+ },
+ bool: {
+ src: `_ = ${builtin}(false);`,
+ pass: true,
+ },
+ i32: {
+ src: `_ = ${builtin}(1i);`,
+ pass: false,
+ },
+ u32: {
+ src: `_ = ${builtin}(1u);`,
+ pass: false,
+ },
+ f32: {
+ src: `_ = ${builtin}(1.0f);`,
+ pass: false,
+ },
+ f16: {
+ src: `_ = ${builtin}(1.0h);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = ${builtin}(vec2<bool>(false, true));`,
+ pass: true,
+ },
+ vec2_bool_implicit: {
+ src: `_ = ${builtin}(vec2(false, true));`,
+ pass: true,
+ },
+ vec3_bool_implicit: {
+ src: `_ = ${builtin}(vec3(true));`,
+ pass: true,
+ },
+ vec_i32: {
+ src: `_ = ${builtin}(vec2<i32>(1, 1));`,
+ pass: false,
+ },
+ vec_u32: {
+ src: `_ = ${builtin}(vec2<u32>(1, 1));`,
+ pass: false,
+ },
+ vec_f32: {
+ src: `_ = ${builtin}(vec2<f32>(1, 1));`,
+ pass: false,
+ },
+ vec_f16: {
+ src: `_ = ${builtin}(vec2<f16>(1, 1));`,
+ pass: false,
+ },
+ matrix: {
+ src: `_ = ${builtin}(mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = ${builtin}(a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<bool, 5>;
+ _ = ${builtin}(a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = ${builtin}(k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = ${builtin}(a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = ${builtin}(read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = true;
+ let p: ptr<function, bool> = &a;
+ _ = ${builtin}(p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = true;
+ let p: ptr<function, bool> = &a;
+ _ = ${builtin}(*p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = ${builtin}(s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = ${builtin}(t);`,
+ pass: false,
+ },
+ no_args: {
+ src: `_ = ${builtin}();`,
+ pass: false,
+ },
+ too_many_args: {
+ src: `_ = ${builtin}(true, true);`,
+ pass: false,
+ },
+};
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(true); }`);
+ });
+
+g.test('arguments')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .beforeAllSubcases(t => {
+ if (t.params.test.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const enables = t.params.test.includes('f16') ? 'enable f16;' : '';
+ const code = `
+ ${enables}
+ alias bool_alias = bool;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: bool,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+ }`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/any.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/any.spec.ts
new file mode 100644
index 0000000000..9f328bbbf3
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/any.spec.ts
@@ -0,0 +1,191 @@
+const builtin = 'any';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import { Type, elementTypeOf, kAllScalarsAndVectors } from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import { validateConstOrOverrideBuiltinEval } from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kArgumentTypes = objectsToRecord(kAllScalarsAndVectors);
+
+g.test('argument_types')
+ .desc(
+ `
+Validates that scalar and vector arguments are rejected by ${builtin}() if not bool or vecN<bool>
+`
+ )
+ .params(u => u.combine('type', keysOf(kArgumentTypes)))
+ .fn(t => {
+ const type = kArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ elementTypeOf(type) === Type.bool,
+ [type.create(0)],
+ 'constant',
+ /* returnType */ Type.bool
+ );
+ });
+
+const kTests = {
+ valid: {
+ src: `_ = ${builtin}(true);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = ${builtin}(bool_alias(true));`,
+ pass: true,
+ },
+ bool: {
+ src: `_ = ${builtin}(false);`,
+ pass: true,
+ },
+ i32: {
+ src: `_ = ${builtin}(1i);`,
+ pass: false,
+ },
+ u32: {
+ src: `_ = ${builtin}(1u);`,
+ pass: false,
+ },
+ f32: {
+ src: `_ = ${builtin}(1.0f);`,
+ pass: false,
+ },
+ f16: {
+ src: `_ = ${builtin}(1.0h);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = ${builtin}(vec2<bool>(false, true));`,
+ pass: true,
+ },
+ vec2_bool_implicit: {
+ src: `_ = ${builtin}(vec2(false, true));`,
+ pass: true,
+ },
+ vec3_bool_implicit: {
+ src: `_ = ${builtin}(vec3(true));`,
+ pass: true,
+ },
+ vec_i32: {
+ src: `_ = ${builtin}(vec2<i32>(1, 1));`,
+ pass: false,
+ },
+ vec_u32: {
+ src: `_ = ${builtin}(vec2<u32>(1, 1));`,
+ pass: false,
+ },
+ vec_f32: {
+ src: `_ = ${builtin}(vec2<f32>(1, 1));`,
+ pass: false,
+ },
+ vec_f16: {
+ src: `_ = ${builtin}(vec2<f16>(1, 1));`,
+ pass: false,
+ },
+ matrix: {
+ src: `_ = ${builtin}(mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = ${builtin}(a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<bool, 5>;
+ _ = ${builtin}(a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = ${builtin}(k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = ${builtin}(a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = ${builtin}(read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = true;
+ let p: ptr<function, bool> = &a;
+ _ = ${builtin}(p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = true;
+ let p: ptr<function, bool> = &a;
+ _ = ${builtin}(*p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = ${builtin}(s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = ${builtin}(t);`,
+ pass: false,
+ },
+ no_args: {
+ src: `_ = ${builtin}();`,
+ pass: false,
+ },
+ too_many_args: {
+ src: `_ = ${builtin}(true, true);`,
+ pass: false,
+ },
+};
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(true); }`);
+ });
+
+g.test('arguments')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .beforeAllSubcases(t => {
+ if (t.params.test.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const enables = t.params.test.includes('f16') ? 'enable f16;' : '';
+ const code = `
+ ${enables}
+ alias bool_alias = bool;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: bool,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+ }`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/arrayLength.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/arrayLength.spec.ts
new file mode 100644
index 0000000000..27d8814b14
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/arrayLength.spec.ts
@@ -0,0 +1,109 @@
+export const description = `
+Validation tests for arrayLength builtins.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('bool_type')
+ .specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin')
+ .desc(
+ `
+arrayLength accepts only runtime-sized arrays
+`
+ )
+ .fn(t => {
+ const code = `
+@compute @workgroup_size(1)
+fn main() {
+ var b = true;
+ _ = arrayLength(&b);
+}`;
+
+ t.expectCompileResult(false, code);
+ });
+
+const atomic_types = ['u32', 'i32'].map(j => `atomic<${j}>`);
+const vec_types = [2, 3, 4]
+ .map(i => ['i32', 'u32', 'f32', 'f16'].map(j => `vec${i}<${j}>`))
+ .reduce((a, c) => a.concat(c), []);
+const f32_matrix_types = [2, 3, 4]
+ .map(i => [2, 3, 4].map(j => `mat${i}x${j}f`))
+ .reduce((a, c) => a.concat(c), []);
+const f16_matrix_types = [2, 3, 4]
+ .map(i => [2, 3, 4].map(j => `mat${i}x${j}<f16>`))
+ .reduce((a, c) => a.concat(c), []);
+
+g.test('type')
+ .specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin')
+ .desc(
+ `
+arrayLength accepts only runtime-sized arrays
+`
+ )
+ .params(u =>
+ u.combine('type', [
+ 'i32',
+ 'u32',
+ 'f32',
+ 'f16',
+ ...f32_matrix_types,
+ ...f16_matrix_types,
+ ...vec_types,
+ ...atomic_types,
+ 'T',
+ 'array<i32, 2>',
+ 'array<i32>',
+ ])
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const code = `
+struct T {
+ b: i32,
+}
+struct S {
+ ary: ${t.params.type}
+}
+
+@group(0) @binding(0) var<storage, read_write> items: S;
+
+@compute @workgroup_size(1)
+fn main() {
+ _ = arrayLength(&items.ary);
+}`;
+
+ t.expectCompileResult(t.params.type === 'array<i32>', code);
+ });
+
+// Note, the `write` case actually fails because you can't declare a storage buffer of
+// access_mode `write`.
+g.test('access_mode')
+ .specURL('https://www.w3.org/TR/WGSL/#arrayLength-builtin')
+ .desc(
+ `
+arrayLength runtime-sized array must have an access_mode of read or read_write
+`
+ )
+ .params(u => u.combine('mode', ['read', 'read_write', 'write']))
+ .fn(t => {
+ const code = `
+struct S {
+ ary: array<i32>,
+}
+
+@group(0) @binding(0) var<storage, ${t.params.mode}> items: S;
+
+@compute @workgroup_size(1)
+fn main() {
+ _ = arrayLength(&items.ary);
+}`;
+
+ t.expectCompileResult(t.params.mode !== 'write', code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asin.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asin.spec.ts
index 8af7706169..16a193aec4 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asin.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asin.spec.ts
@@ -6,18 +6,18 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
+import { absBigInt } from '../../../../../util/math.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinusTwoToTwo,
+ minusTwoToTwoRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -25,7 +25,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -39,15 +39,23 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(kMinusTwoToTwo, fullRangeForType(kValuesTypes[u.type])))
+ .expand('value', u =>
+ unique(
+ minusTwoToTwoRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
- const expectedResult = Math.abs(t.params.value) <= 1;
+ const expectedResult =
+ typeof t.params.value === 'bigint'
+ ? absBigInt(t.params.value) <= 1n
+ : Math.abs(t.params.value) <= 1;
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -57,7 +65,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -71,8 +79,137 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(0)],
'constant'
);
});
+
+const kTests = {
+ valid: {
+ src: `_ = asin(1);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = asin(f32_alias(1));`,
+ pass: true,
+ },
+
+ bool: {
+ src: `_ = asin(false);`,
+ pass: false,
+ },
+ i32: {
+ src: `_ = asin(1i);`,
+ pass: false,
+ },
+ u32: {
+ src: `_ = asin(1u);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = asin(vec2<bool>(false, true));`,
+ pass: false,
+ },
+ vec_i32: {
+ src: `_ = asin(vec2<i32>(1, 1));`,
+ pass: false,
+ },
+ vec_u32: {
+ src: `_ = asin(vec2<u32>(1, 1));`,
+ pass: false,
+ },
+ matrix: {
+ src: `_ = asin(mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = asin(a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<u32, 5>;
+ _ = asin(a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = asin(k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = asin(a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = asin(read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = asin(p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = asin(*p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = asin(s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = asin(t);`,
+ pass: false,
+ },
+ no_params: {
+ src: `_ = asin();`,
+ pass: false,
+ },
+ too_many_params: {
+ src: `_ = asin(1, 2);`,
+ pass: false,
+ },
+
+ greater_then_one: {
+ src: `_ = asin(1.1f);`,
+ pass: false,
+ },
+ less_then_negative_one: {
+ src: `_ = asin(-1.1f);`,
+ pass: false,
+ },
+};
+
+g.test('parameters')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const code = `
+alias f32_alias = f32;
+
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: texture_2d<f32>;
+
+var<workgroup> a: atomic<u32>;
+
+struct A {
+ i: u32,
+}
+struct B {
+ arry: array<u32>,
+}
+@group(0) @binding(3) var<storage> k: B;
+
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asinh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asinh.spec.ts
index 4558d30966..285ff2dcd2 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asinh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/asinh.spec.ts
@@ -6,19 +6,19 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
+ Type,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
-import { linearRange } from '../../../../../util/math.js';
+import { linearRange, linearRangeBigInt } from '../../../../../util/math.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
+ rangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -26,7 +26,12 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+const additionalRangeForType = rangeForType(
+ linearRange(-2000, 2000, 10),
+ linearRangeBigInt(-2000n, 2000n, 10)
+);
g.test('values')
.desc(
@@ -41,17 +46,21 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
.expand('value', u =>
- unique(fullRangeForType(kValuesTypes[u.type]), linearRange(-2000, 2000, 10))
+ unique(fullRangeForType(kValuesTypes[u.type]), additionalRangeForType(kValuesTypes[u.type]))
)
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
- const expectedResult = isRepresentable(Math.asinh(t.params.value), elementType(type));
+ const expectedResult = isRepresentable(
+ Math.asinh(Number(t.params.value)),
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -61,7 +70,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -75,8 +84,128 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(1)],
'constant'
);
});
+
+const kTests = {
+ valid: {
+ src: `_ = asinh(1);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = asinh(f32_alias(1));`,
+ pass: true,
+ },
+
+ bool: {
+ src: `_ = asinh(false);`,
+ pass: false,
+ },
+ i32: {
+ src: `_ = asinh(1i);`,
+ pass: false,
+ },
+ u32: {
+ src: `_ = asinh(1u);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = asinh(vec2<bool>(false, true));`,
+ pass: false,
+ },
+ vec_i32: {
+ src: `_ = asinh(vec2<i32>(1, 1));`,
+ pass: false,
+ },
+ vec_u32: {
+ src: `_ = asinh(vec2<u32>(1, 1));`,
+ pass: false,
+ },
+ matrix: {
+ src: `_ = asinh(mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = asinh(a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<u32, 5>;
+ _ = asinh(a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = asinh(k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = asinh(a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = asinh(read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = asinh(p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = asinh(*p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = asinh(s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = asinh(t);`,
+ pass: false,
+ },
+ no_params: {
+ src: `_ = asinh();`,
+ pass: false,
+ },
+ too_many_params: {
+ src: `_ = asinh(1, 2);`,
+ pass: false,
+ },
+};
+
+g.test('parameters')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const code = `
+alias f32_alias = f32;
+
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: texture_2d<f32>;
+
+var<workgroup> a: atomic<u32>;
+
+struct A {
+ i: u32,
+}
+struct B {
+ arry: array<u32>,
+}
+@group(0) @binding(3) var<storage> k: B;
+
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan.spec.ts
index 3080f4e971..c5e964084d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan.spec.ts
@@ -6,18 +6,17 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinus3PiTo3Pi,
+ minusThreePiToThreePiRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -25,7 +24,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -39,10 +38,15 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(kMinus3PiTo3Pi, fullRangeForType(kValuesTypes[u.type])))
+ .expand('value', u =>
+ unique(
+ minusThreePiToThreePiRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -58,7 +62,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -72,8 +76,128 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(0)],
'constant'
);
});
+
+const kTests = {
+ valid: {
+ src: `_ = atan(1);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = atan(f32_alias(1));`,
+ pass: true,
+ },
+
+ bool: {
+ src: `_ = atan(false);`,
+ pass: false,
+ },
+ i32: {
+ src: `_ = atan(1i);`,
+ pass: false,
+ },
+ u32: {
+ src: `_ = atan(1u);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = atan(vec2<bool>(false, true));`,
+ pass: false,
+ },
+ vec_i32: {
+ src: `_ = atan(vec2<i32>(1, 1));`,
+ pass: false,
+ },
+ vec_u32: {
+ src: `_ = atan(vec2<u32>(1, 1));`,
+ pass: false,
+ },
+ matrix: {
+ src: `_ = atan(mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = atan(a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<u32, 5>;
+ _ = atan(a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = atan(k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = atan(a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = atan(read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = atan(p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = atan(*p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = atan(s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = atan(t);`,
+ pass: false,
+ },
+ no_params: {
+ src: `_ = atan();`,
+ pass: false,
+ },
+ too_many_params: {
+ src: `_ = atan(1, 2);`,
+ pass: false,
+ },
+};
+
+g.test('parameters')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const code = `
+alias f32_alias = f32;
+
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: texture_2d<f32>;
+
+var<workgroup> a: atomic<u32>;
+
+struct A {
+ i: u32,
+}
+struct B {
+ arry: array<u32>,
+}
+@group(0) @binding(3) var<storage> k: B;
+
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan2.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan2.spec.ts
index 33f1970697..20998bfe76 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan2.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atan2.spec.ts
@@ -6,13 +6,13 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- Vector,
- VectorType,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ VectorValue,
+ kFloatScalarsAndVectors,
+ kConcreteIntegerScalarsAndVectors,
+ kAllMatrices,
+ kAllBoolScalarsAndVectors,
+ scalarTypeOf,
+ Type,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -20,7 +20,7 @@ import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kSparseMinus3PiTo3Pi,
+ sparseMinusThreePiToThreePiRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -28,7 +28,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -42,19 +42,29 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('y', u => unique(kSparseMinus3PiTo3Pi, fullRangeForType(kValuesTypes[u.type], 4)))
- .expand('x', u => unique(kSparseMinus3PiTo3Pi, fullRangeForType(kValuesTypes[u.type], 4)))
+ .expand('y', u =>
+ unique(
+ sparseMinusThreePiToThreePiRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type], 4)
+ )
+ )
+ .expand('x', u =>
+ unique(
+ sparseMinusThreePiToThreePiRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type], 4)
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
const expectedResult = isRepresentable(
- Math.abs(Math.atan2(t.params.y, t.params.x)),
- elementType(type)
+ Math.abs(Math.atan2(Number(t.params.x), Number(t.params.y))),
+ scalarTypeOf(type)
);
validateConstOrOverrideBuiltinEval(
t,
@@ -65,42 +75,275 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kInvalidArgumentTypes = objectsToRecord([
+ Type.f32,
+ ...kConcreteIntegerScalarsAndVectors,
+ ...kAllBoolScalarsAndVectors,
+ ...kAllMatrices,
+]);
-g.test('integer_argument_y')
+g.test('invalid_argument_y')
.desc(
`
Validates that scalar and vector integer arguments are rejected by ${builtin}()
`
)
- .params(u => u.combine('type', keysOf(kIntegerArgumentTypes)))
+ .params(u => u.combine('type', keysOf(kInvalidArgumentTypes)))
.fn(t => {
- const yTy = kIntegerArgumentTypes[t.params.type];
- const xTy = yTy instanceof Vector ? new VectorType(yTy.size, TypeF32) : TypeF32;
+ const yTy = kInvalidArgumentTypes[t.params.type];
+ const xTy = yTy instanceof VectorValue ? Type.vec(yTy.size, Type.f32) : Type.f32;
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ yTy === TypeF32,
+ /* expectedResult */ yTy === Type.f32,
[yTy.create(1), xTy.create(1)],
'constant'
);
});
-g.test('integer_argument_x')
+g.test('invalid_argument_x')
.desc(
`
Validates that scalar and vector integer arguments are rejected by ${builtin}()
`
)
- .params(u => u.combine('type', keysOf(kIntegerArgumentTypes)))
+ .params(u => u.combine('type', keysOf(kInvalidArgumentTypes)))
.fn(t => {
- const xTy = kIntegerArgumentTypes[t.params.type];
- const yTy = xTy instanceof Vector ? new VectorType(xTy.size, TypeF32) : TypeF32;
+ const xTy = kInvalidArgumentTypes[t.params.type];
+ const yTy = xTy instanceof VectorValue ? Type.vec(xTy.size, Type.f32) : Type.f32;
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ xTy === TypeF32,
+ /* expectedResult */ xTy === Type.f32,
[yTy.create(1), xTy.create(1)],
'constant'
);
});
+
+const kTests = {
+ af: {
+ src: `_ = atan2(1.2, 2.2);`,
+ pass: true,
+ is_f16: false,
+ },
+ ai: {
+ src: `_ = atan2(1, 2);`,
+ pass: true,
+ is_f16: false,
+ },
+ ai_af: {
+ src: `_ = atan2(1, 2.1);`,
+ pass: true,
+ is_f16: false,
+ },
+ af_ai: {
+ src: `_ = atan2(1.2, 2);`,
+ pass: true,
+ is_f16: false,
+ },
+ ai_f32: {
+ src: `_ = atan2(1, 1.2f);`,
+ pass: true,
+ is_f16: false,
+ },
+ f32_ai: {
+ src: `_ = atan2(1.2f, 1);`,
+ pass: true,
+ is_f16: false,
+ },
+ af_f32: {
+ src: `_ = atan2(1.2, 2.2f);`,
+ pass: true,
+ is_f16: false,
+ },
+ f32_af: {
+ src: `_ = atan2(2.2f, 1.2);`,
+ pass: true,
+ is_f16: false,
+ },
+ f16_ai: {
+ src: `_ = atan2(1.2h, 1);`,
+ pass: true,
+ is_f16: true,
+ },
+ ai_f16: {
+ src: `_ = atan2(1, 1.2h);`,
+ pass: true,
+ is_f16: true,
+ },
+ af_f16: {
+ src: `_ = atan2(1.2, 1.2h);`,
+ pass: true,
+ is_f16: true,
+ },
+ f16_af: {
+ src: `_ = atan2(1.2h, 1.2);`,
+ pass: true,
+ is_f16: true,
+ },
+
+ mixed_types: {
+ src: `_ = atan2(1.2f, vec2(1.2f));`,
+ pass: false,
+ is_f16: false,
+ },
+ mixed_types_2: {
+ src: `_ = atan2(vec2(1.2f), 1.2f);`,
+ pass: false,
+ is_f16: false,
+ },
+ f16_f32: {
+ src: `_ = atan2(1.2h, 1.2f);`,
+ pass: false,
+ is_f16: true,
+ },
+ u32_f32: {
+ src: `_ = atan2(1u, 1.2f);`,
+ pass: false,
+ is_f16: false,
+ },
+ f32_u32: {
+ src: `_ = atan2(1.2f, 1u);`,
+ pass: false,
+ is_f16: false,
+ },
+ f32_i32: {
+ src: `_ = atan2(1.2f, 1i);`,
+ pass: false,
+ is_f16: false,
+ },
+ i32_f32: {
+ src: `_ = atan2(1i, 1.2f);`,
+ pass: false,
+ is_f16: false,
+ },
+ f32_bool: {
+ src: `_ = atan2(1.2f, true);`,
+ pass: false,
+ is_f16: false,
+ },
+ bool_f32: {
+ src: `_ = atan2(false, 1.2f);`,
+ pass: false,
+ is_f16: false,
+ },
+ vec_f32: {
+ src: `_ = atan2(vec2(1i), vec2(1.2f));`,
+ pass: false,
+ is_f16: false,
+ },
+ f32_vec: {
+ src: `_ = atan2(vec2(1.2f), vec2(1i));`,
+ pass: false,
+ is_f16: false,
+ },
+ matrix: {
+ src: `_ = atan2(mat2x2(1, 1, 1, 1), mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ is_f16: false,
+ },
+ atomic: {
+ src: ` _ = atan2(a, a);`,
+ pass: false,
+ is_f16: false,
+ },
+ array: {
+ src: `var a: array<u32, 5>;
+ _ = atan2(a, a);`,
+ pass: false,
+ is_f16: false,
+ },
+ array_runtime: {
+ src: `_ = atan2(k.arry, k.arry);`,
+ pass: false,
+ is_f16: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = atan2(a, a);`,
+ pass: false,
+ is_f16: false,
+ },
+ enumerant: {
+ src: `_ = atan2(read_write, read_write);`,
+ pass: false,
+ is_f16: false,
+ },
+ ptr: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = atan2(p, p);`,
+ pass: false,
+ is_f16: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = 1f;
+ let p: ptr<function, f32> = &a;
+ _ = atan2(*p, *p);`,
+ pass: true,
+ is_f16: false,
+ },
+ sampler: {
+ src: `_ = atan2(s, s);`,
+ pass: false,
+ is_f16: false,
+ },
+ texture: {
+ src: `_ = atan2(t, t);`,
+ pass: false,
+ is_f16: false,
+ },
+ no_params: {
+ src: `_ = atan2();`,
+ pass: false,
+ is_f16: false,
+ },
+ too_many_params: {
+ src: `_ = atan2(1, 2, 3);`,
+ pass: false,
+ is_f16: false,
+ },
+};
+
+g.test('parameters')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .beforeAllSubcases(t => {
+ if (kTests[t.params.test].is_f16 === true) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const code = `
+alias f32_alias = f32;
+
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: texture_2d<f32>;
+
+var<workgroup> a: atomic<u32>;
+
+struct A {
+ i: u32,
+}
+struct B {
+ arry: array<u32>,
+}
+@group(0) @binding(3) var<storage> k: B;
+
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(1, 2); }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atanh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atanh.spec.ts
index 63a96f0f70..4eae8a71f0 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atanh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atanh.spec.ts
@@ -6,18 +6,18 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
+import { absBigInt } from '../../../../../util/math.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinusTwoToTwo,
+ minusTwoToTwoRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -25,7 +25,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -39,15 +39,23 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(kMinusTwoToTwo, fullRangeForType(kValuesTypes[u.type])))
+ .expand('value', u =>
+ unique(
+ minusTwoToTwoRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
- const expectedResult = Math.abs(t.params.value) < 1;
+ const expectedResult =
+ typeof t.params.value === 'bigint'
+ ? absBigInt(t.params.value) < 1n
+ : Math.abs(t.params.value) < 1;
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -57,7 +65,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -71,8 +79,145 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(0)],
'constant'
);
});
+
+const kTests = {
+ valid: {
+ src: `_ = atanh(.1);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = atanh(f32_alias(.1));`,
+ pass: true,
+ },
+
+ bool: {
+ src: `_ = atanh(false);`,
+ pass: false,
+ },
+ i32: {
+ src: `_ = atanh(0i);`,
+ pass: false,
+ },
+ u32: {
+ src: `_ = atanh(0u);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = atanh(vec2<bool>(false, true));`,
+ pass: false,
+ },
+ vec_i32: {
+ src: `_ = atanh(vec2<i32>(0, 0));`,
+ pass: false,
+ },
+ vec_u32: {
+ src: `_ = atanh(vec2<u32>(0, 0));`,
+ pass: false,
+ },
+ matrix: {
+ src: `_ = atanh(mat2x2(0, 0, 0, 0));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = atanh(a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<u32, 5>;
+ _ = atanh(a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = atanh(k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = atanh(a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = atanh(read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = 0f;
+ let p: ptr<function, f32> = &a;
+ _ = atanh(p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = 0f;
+ let p: ptr<function, f32> = &a;
+ _ = atanh(*p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = atanh(s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = atanh(t);`,
+ pass: false,
+ },
+ no_params: {
+ src: `_ = atanh();`,
+ pass: false,
+ },
+ too_many_params: {
+ src: `_ = atanh(0, .2);`,
+ pass: false,
+ },
+
+ one: {
+ src: `_ = atanh(1f);`,
+ pass: false,
+ },
+ greater_then_one: {
+ src: `_ = atanh(1.1f);`,
+ pass: false,
+ },
+ negative_one: {
+ src: `_ = atanh(-1f);`,
+ pass: false,
+ },
+ less_then_negative_one: {
+ src: `_ = atanh(-1.1f);`,
+ pass: false,
+ },
+};
+
+g.test('parameters')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const code = `
+alias f32_alias = f32;
+
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: texture_2d<f32>;
+
+var<workgroup> a: atomic<u32>;
+
+struct A {
+ i: u32,
+}
+struct B {
+ arry: array<u32>,
+}
+@group(0) @binding(3) var<storage> k: B;
+
+
+@vertex
+fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+}`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atomics.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atomics.spec.ts
index 57c5aae613..fdb85664d2 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atomics.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/atomics.spec.ts
@@ -8,18 +8,44 @@ import { ShaderValidationTest } from '../../../shader_validation_test.js';
export const g = makeTestGroup(ShaderValidationTest);
-const kAtomicOps = {
- add: { src: 'atomicAdd(&a,1)' },
- sub: { src: 'atomicSub(&a,1)' },
- max: { src: 'atomicMax(&a,1)' },
- min: { src: 'atomicMin(&a,1)' },
- and: { src: 'atomicAnd(&a,1)' },
- or: { src: 'atomicOr(&a,1)' },
- xor: { src: 'atomicXor(&a,1)' },
- load: { src: 'atomicLoad(&a)' },
- store: { src: 'atomicStore(&a,1)' },
- exchange: { src: 'atomicExchange(&a,1)' },
- compareexchangeweak: { src: 'atomicCompareExchangeWeak(&a,1,1)' },
+interface stringToString {
+ (a: string): string;
+}
+
+const kAtomicOps: Record<string, stringToString> = {
+ add: (a: string): string => {
+ return `atomicAdd(${a},1)`;
+ },
+ sub: (a: string): string => {
+ return `atomicSub(${a},1)`;
+ },
+ max: (a: string): string => {
+ return `atomicMax(${a},1)`;
+ },
+ min: (a: string): string => {
+ return `atomicMin(${a},1)`;
+ },
+ and: (a: string): string => {
+ return `atomicAnd(${a},1)`;
+ },
+ or: (a: string): string => {
+ return `atomicOr(${a},1)`;
+ },
+ xor: (a: string): string => {
+ return `atomicXor(${a},1)`;
+ },
+ load: (a: string): string => {
+ return `atomicLoad(${a})`;
+ },
+ store: (a: string): string => {
+ return `atomicStore(${a},1)`;
+ },
+ exchange: (a: string): string => {
+ return `atomicExchange(${a},1)`;
+ },
+ compareexchangeweak: (a: string): string => {
+ return `atomicCompareExchangeWeak(${a},1,1)`;
+ },
};
g.test('stage')
@@ -35,7 +61,7 @@ Atomic built-in functions must not be used in a vertex shader stage.
.combine('atomicOp', keysOf(kAtomicOps))
)
.fn(t => {
- const atomicOp = kAtomicOps[t.params.atomicOp].src;
+ const atomicOp = kAtomicOps[t.params.atomicOp](`&a`);
let code = `
@group(0) @binding(0) var<storage, read_write> a: atomic<i32>;
`;
@@ -68,3 +94,186 @@ Atomic built-in functions must not be used in a vertex shader stage.
const pass = t.params.stage !== 'vertex';
t.expectCompileResult(pass, code);
});
+
+function generateAtomicCode(
+ type: string,
+ access: string,
+ aspace: string,
+ style: string,
+ op: string
+): string {
+ let moduleVar = ``;
+ let functionVar = ``;
+ let param = ``;
+ let aParam = ``;
+ if (style === 'var') {
+ aParam = `&a`;
+ switch (aspace) {
+ case 'storage':
+ moduleVar = `@group(0) @binding(0) var<storage, ${access}> a : atomic<${type}>;\n`;
+ break;
+ case 'workgroup':
+ moduleVar = `var<workgroup> a : atomic<${type}>;\n`;
+ break;
+ case 'uniform':
+ moduleVar = `@group(0) @binding(0) var<uniform> a : atomic<${type}>;\n`;
+ break;
+ case 'private':
+ moduleVar = `var<private> a : atomic<${type}>;\n`;
+ break;
+ case 'function':
+ functionVar = `var a : atomic<${type}>;\n`;
+ break;
+ default:
+ break;
+ }
+ } else {
+ const aspaceParam = aspace === 'storage' ? `, ${access}` : ``;
+ param = `p : ptr<${aspace}, atomic<${type}>${aspaceParam}>`;
+ aParam = `p`;
+ }
+
+ return `
+${moduleVar}
+fn foo(${param}) {
+ ${functionVar}
+ ${kAtomicOps[op](aParam)};
+}
+`;
+}
+
+g.test('atomic_parameterization')
+ .desc('Tests the valid atomic parameters')
+ .params(u =>
+ u
+ .combine('op', keysOf(kAtomicOps))
+ .beginSubcases()
+ .combine('aspace', ['storage', 'workgroup', 'private', 'uniform', 'function'] as const)
+ .combine('access', ['read', 'read_write'] as const)
+ .combine('type', ['i32', 'u32'] as const)
+ .combine('style', ['param', 'var'] as const)
+ .filter(t => {
+ switch (t.aspace) {
+ case 'uniform':
+ return t.style === 'param' && t.access === 'read';
+ case 'workgroup':
+ return t.access === 'read_write';
+ case 'function':
+ case 'private':
+ return t.style === 'param' && t.access === 'read_write';
+ default:
+ return true;
+ }
+ })
+ )
+ .fn(t => {
+ if (
+ t.params.style === 'param' &&
+ !(t.params.aspace === 'function' || t.params.aspace === 'private')
+ ) {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+ }
+
+ const aspaceOK = t.params.aspace === 'storage' || t.params.aspace === 'workgroup';
+ const accessOK = t.params.access === 'read_write';
+ t.expectCompileResult(
+ aspaceOK && accessOK,
+ generateAtomicCode(
+ t.params.type,
+ t.params.access,
+ t.params.aspace,
+ t.params.style,
+ t.params.op
+ )
+ );
+ });
+
+g.test('data_parameters')
+ .desc('Validates that data parameters must match atomic type (or be implicitly convertible)')
+ .params(u =>
+ u
+ .combine('op', [
+ 'atomicStore',
+ 'atomicAdd',
+ 'atomicSub',
+ 'atomicMax',
+ 'atomicMin',
+ 'atomicAnd',
+ 'atomicOr',
+ 'atomicXor',
+ 'atomicExchange',
+ 'atomicCompareExchangeWeak1',
+ 'atomicCompareExchangeWeak2',
+ ] as const)
+ .beginSubcases()
+ .combine('atomicType', ['i32', 'u32'] as const)
+ .combine('dataType', ['i32', 'u32', 'f32', 'AbstractInt'] as const)
+ )
+ .fn(t => {
+ let dataValue = '';
+ switch (t.params.dataType) {
+ case 'i32':
+ dataValue = '1i';
+ break;
+ case 'u32':
+ dataValue = '1u';
+ break;
+ case 'f32':
+ dataValue = '1f';
+ break;
+ case 'AbstractInt':
+ dataValue = '1';
+ break;
+ }
+ let op = '';
+ switch (t.params.op) {
+ case 'atomicCompareExchangeWeak1':
+ op = `atomicCompareExchangeWeak(&a, ${dataValue}, 1)`;
+ break;
+ case 'atomicCompareExchangeWeak2':
+ op = `atomicCompareExchangeWeak(&a, 1, ${dataValue})`;
+ break;
+ default:
+ op = `${t.params.op}(&a, ${dataValue})`;
+ break;
+ }
+ const code = `
+var<workgroup> a : atomic<${t.params.atomicType}>;
+fn foo() {
+ ${op};
+}
+`;
+
+ const expect = t.params.atomicType === t.params.dataType || t.params.dataType === 'AbstractInt';
+ t.expectCompileResult(expect, code);
+ });
+
+g.test('return_types')
+ .desc('Validates return types of atomics')
+ .params(u =>
+ u
+ .combine('op', keysOf(kAtomicOps))
+ .beginSubcases()
+ .combine('atomicType', ['i32', 'u32'] as const)
+ .combine('returnType', ['i32', 'u32', 'f32'] as const)
+ )
+ .fn(t => {
+ let op = `${kAtomicOps[t.params.op]('&a')}`;
+ switch (t.params.op) {
+ case 'compareexchangeweak':
+ op = `let tmp : ${t.params.returnType} = ${op}.old_value`;
+ break;
+ default:
+ op = `let tmp : ${t.params.returnType} = ${op}`;
+ break;
+ }
+ const code = `
+var<workgroup> a : atomic<${t.params.atomicType}>;
+fn foo() {
+ ${op};
+}
+`;
+
+ const expect = t.params.atomicType === t.params.returnType && t.params.op !== 'store';
+ t.expectCompileResult(expect, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/barriers.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/barriers.spec.ts
new file mode 100644
index 0000000000..5a4ef14657
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/barriers.spec.ts
@@ -0,0 +1,109 @@
+export const description = `
+Validation tests for {storage,texture,workgroup}Barrier() builtins.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kEntryPoints = {
+ none: { supportsBarrier: true, code: `` },
+ compute: {
+ supportsBarrier: true,
+ code: `@compute @workgroup_size(1)
+fn main() {
+ foo();
+}`,
+ },
+ vertex: {
+ supportsBarrier: false,
+ code: `@vertex
+fn main() -> @builtin(position) vec4f {
+ foo();
+ return vec4f();
+}`,
+ },
+ fragment: {
+ supportsBarrier: false,
+ code: `@fragment
+fn main() {
+ foo();
+}`,
+ },
+ compute_and_fragment: {
+ supportsBarrier: false,
+ code: `@compute @workgroup_size(1)
+fn main1() {
+ foo();
+}
+
+@fragment
+fn main2() {
+ foo();
+}
+`,
+ },
+ fragment_without_call: {
+ supportsBarrier: true,
+ code: `@fragment
+fn main() {
+}
+`,
+ },
+};
+
+g.test('only_in_compute')
+ .specURL('https://www.w3.org/TR/WGSL/#sync-builtin-functions')
+ .desc(
+ `
+Synchronization functions must only be used in the compute shader stage.
+`
+ )
+ .params(u =>
+ u
+ .combine('entry_point', keysOf(kEntryPoints))
+ .combine('call', ['bar', 'storageBarrier', 'textureBarrier', 'workgroupBarrier'])
+ )
+ .fn(t => {
+ if (t.params.call.startsWith('textureBarrier')) {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+ }
+
+ const config = kEntryPoints[t.params.entry_point];
+ const code = `
+${config.code}
+fn bar() {}
+
+fn foo() {
+ ${t.params.call}();
+}`;
+ t.expectCompileResult(t.params.call === 'bar' || config.supportsBarrier, code);
+ });
+
+g.test('no_return_value')
+ .specURL('https://www.w3.org/TR/WGSL/#sync-builtin-functions')
+ .desc(
+ `
+Barrier functions do not return a value.
+`
+ )
+ .params(u =>
+ u
+ .combine('assign', [false, true])
+ .combine('rhs', ['bar', 'storageBarrier', 'textureBarrier', 'workgroupBarrier'])
+ )
+ .fn(t => {
+ if (t.params.rhs.startsWith('textureBarrier')) {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+ }
+
+ const code = `
+fn bar() {}
+
+fn foo() {
+ ${t.params.assign ? '_ = ' : ''} ${t.params.rhs}();
+}`;
+ t.expectCompileResult(!t.params.assign || t.params.rhs === 'bar()', code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/ceil.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/ceil.spec.ts
index 0f287907f8..951958d02c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/ceil.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/ceil.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -23,7 +22,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -36,10 +35,11 @@ Validates that constant evaluation and override evaluation of ${builtin}() never
.combine('stage', kConstantAndOverrideStages)
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -54,7 +54,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() never
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -68,7 +68,7 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(0)],
'constant'
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/clamp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/clamp.spec.ts
index 1cf28ffc2b..9a23328d65 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/clamp.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/clamp.spec.ts
@@ -6,9 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- elementType,
- kAllFloatAndIntegerScalarsAndVectors,
+ Type,
+ kFloatScalarsAndVectors,
+ kConcreteIntegerScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -21,7 +22,10 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatAndIntegerScalarsAndVectors);
+const kValuesTypes = objectsToRecord([
+ ...kFloatScalarsAndVectors,
+ ...kConcreteIntegerScalarsAndVectors,
+]);
g.test('values')
.desc(
@@ -40,7 +44,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.expand('high', u => fullRangeForType(kValuesTypes[u.type], 4))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/const_override_validation.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/const_override_validation.ts
index 86b88cb159..f3b964a6e3 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/const_override_validation.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/const_override_validation.ts
@@ -2,100 +2,138 @@ import { assert, unreachable } from '../../../../../../common/util/util.js';
import { kValue } from '../../../../../util/constants.js';
import {
Type,
- TypeF16,
Value,
- elementType,
- elementsOf,
+ elementTypeOf,
isAbstractType,
+ scalarElementsOf,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
-import { fullF16Range, fullF32Range, fullF64Range, linearRange } from '../../../../../util/math.js';
+import {
+ scalarF16Range,
+ scalarF32Range,
+ scalarF64Range,
+ linearRange,
+ linearRangeBigInt,
+} from '../../../../../util/math.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
-/// A linear sweep between -2 to 2
-export const kMinusTwoToTwo = linearRange(-2, 2, 10);
-
-/// An array of values ranging from -3π to 3π, with a focus on multiples of π
-export const kMinus3PiTo3Pi = [
- -3 * Math.PI,
- -2.999 * Math.PI,
-
- -2.501 * Math.PI,
- -2.5 * Math.PI,
- -2.499 * Math.PI,
-
- -2.001 * Math.PI,
- -2.0 * Math.PI,
- -1.999 * Math.PI,
-
- -1.501 * Math.PI,
- -1.5 * Math.PI,
- -1.499 * Math.PI,
-
- -1.001 * Math.PI,
- -1.0 * Math.PI,
- -0.999 * Math.PI,
-
- -0.501 * Math.PI,
- -0.5 * Math.PI,
- -0.499 * Math.PI,
-
- -0.001,
- 0,
- 0.001,
-
- 0.499 * Math.PI,
- 0.5 * Math.PI,
- 0.501 * Math.PI,
-
- 0.999 * Math.PI,
- 1.0 * Math.PI,
- 1.001 * Math.PI,
-
- 1.499 * Math.PI,
- 1.5 * Math.PI,
- 1.501 * Math.PI,
-
- 1.999 * Math.PI,
- 2.0 * Math.PI,
- 2.001 * Math.PI,
-
- 2.499 * Math.PI,
- 2.5 * Math.PI,
- 2.501 * Math.PI,
-
- 2.999 * Math.PI,
- 3 * Math.PI,
-] as const;
-
-/// A minimal array of values ranging from -3π to 3π, with a focus on multiples
-/// of π. Used when multiple parameters are being passed in, so the number of
-/// cases becomes the square or more of this list.
-export const kSparseMinus3PiTo3Pi = [
- -3 * Math.PI,
- -2.5 * Math.PI,
- -2.0 * Math.PI,
- -1.5 * Math.PI,
- -1.0 * Math.PI,
- -0.5 * Math.PI,
- 0,
- 0.5 * Math.PI,
- Math.PI,
- 1.5 * Math.PI,
- 2.0 * Math.PI,
- 2.5 * Math.PI,
- 3 * Math.PI,
-] as const;
+/** @returns a function that can select between ranges depending on type */
+export function rangeForType(
+ number_range: readonly number[],
+ bigint_range: readonly bigint[]
+): (type: Type) => readonly (number | bigint)[] {
+ return (type: Type): readonly (number | bigint)[] => {
+ switch (scalarTypeOf(type).kind) {
+ case 'abstract-float':
+ case 'f32':
+ case 'f16':
+ return number_range;
+ case 'abstract-int':
+ return bigint_range;
+ }
+ unreachable(`Received unexpected type '${type}'`);
+ };
+}
+
+/* @returns a linear sweep between -2 to 2 for type */
+// prettier-ignore
+export const minusTwoToTwoRangeForType = rangeForType(
+ linearRange(-2, 2, 10),
+ [ -2n, -1n, 0n, 1n, 2n ]
+);
+
+/* @returns array of values ranging from -3π to 3π, with a focus on multiples of π */
+export const minusThreePiToThreePiRangeForType = rangeForType(
+ [
+ -3 * Math.PI,
+ -2.999 * Math.PI,
+
+ -2.501 * Math.PI,
+ -2.5 * Math.PI,
+ -2.499 * Math.PI,
+
+ -2.001 * Math.PI,
+ -2.0 * Math.PI,
+ -1.999 * Math.PI,
+
+ -1.501 * Math.PI,
+ -1.5 * Math.PI,
+ -1.499 * Math.PI,
+
+ -1.001 * Math.PI,
+ -1.0 * Math.PI,
+ -0.999 * Math.PI,
+
+ -0.501 * Math.PI,
+ -0.5 * Math.PI,
+ -0.499 * Math.PI,
+
+ -0.001,
+ 0,
+ 0.001,
+
+ 0.499 * Math.PI,
+ 0.5 * Math.PI,
+ 0.501 * Math.PI,
+
+ 0.999 * Math.PI,
+ 1.0 * Math.PI,
+ 1.001 * Math.PI,
+
+ 1.499 * Math.PI,
+ 1.5 * Math.PI,
+ 1.501 * Math.PI,
+
+ 1.999 * Math.PI,
+ 2.0 * Math.PI,
+ 2.001 * Math.PI,
+
+ 2.499 * Math.PI,
+ 2.5 * Math.PI,
+ 2.501 * Math.PI,
+
+ 2.999 * Math.PI,
+ 3 * Math.PI,
+ ],
+ [-2n, -1n, 0n, 1n, 2n]
+);
+
+/**
+ * @returns a minimal array of values ranging from -3π to 3π, with a focus on
+ * multiples of π.
+ *
+ * Used when multiple parameters are being passed in, so the number of cases
+ * becomes the square or more of this list. */
+export const sparseMinusThreePiToThreePiRangeForType = rangeForType(
+ [
+ -3 * Math.PI,
+ -2.5 * Math.PI,
+ -2.0 * Math.PI,
+ -1.5 * Math.PI,
+ -1.0 * Math.PI,
+ -0.5 * Math.PI,
+ 0,
+ 0.5 * Math.PI,
+ Math.PI,
+ 1.5 * Math.PI,
+ 2.0 * Math.PI,
+ 2.5 * Math.PI,
+ 3 * Math.PI,
+ ],
+ [-2n, -1n, 0n, 1n, 2n]
+);
/// The evaluation stages to test
export const kConstantAndOverrideStages = ['constant', 'override'] as const;
export type ConstantOrOverrideStage = 'constant' | 'override';
+export type ExecutionStage = 'constant' | 'override' | 'runtime';
/**
* @returns true if evaluation stage `stage` supports expressions of type @p.
*/
export function stageSupportsType(stage: ConstantOrOverrideStage, type: Type) {
- if (stage === 'override' && isAbstractType(elementType(type)!)) {
+ if (stage === 'override' && isAbstractType(elementTypeOf(type)!)) {
// Abstract numerics are concretized before being used in an override expression.
return false;
}
@@ -110,23 +148,26 @@ export function stageSupportsType(stage: ConstantOrOverrideStage, type: Type) {
* @param expectedResult false if an error is expected, true if no error is expected
* @param args the arguments to pass to the builtin
* @param stage the evaluation stage
+ * @param returnType the explicit return type of the result variable, if provided (implicit otherwise)
*/
export function validateConstOrOverrideBuiltinEval(
t: ShaderValidationTest,
builtin: string,
expectedResult: boolean,
args: Value[],
- stage: ConstantOrOverrideStage
+ stage: ConstantOrOverrideStage,
+ returnType?: Type
) {
- const elTys = args.map(arg => elementType(arg.type)!);
- const enables = elTys.some(ty => ty === TypeF16) ? 'enable f16;' : '';
+ const elTys = args.map(arg => elementTypeOf(arg.type)!);
+ const enables = elTys.some(ty => ty === Type.f16) ? 'enable f16;' : '';
+ const optionalVarType = returnType ? `: ${returnType.toString()}` : '';
switch (stage) {
case 'constant': {
t.expectCompileResult(
expectedResult,
`${enables}
-const v = ${builtin}(${args.map(arg => arg.wgsl()).join(', ')});`
+const v ${optionalVarType} = ${builtin}(${args.map(arg => arg.wgsl()).join(', ')});`
);
break;
}
@@ -138,7 +179,7 @@ const v = ${builtin}(${args.map(arg => arg.wgsl()).join(', ')});`
let numOverrides = 0;
for (const arg of args) {
const argOverrides: string[] = [];
- for (const el of elementsOf(arg)) {
+ for (const el of scalarElementsOf(arg)) {
const name = `o${numOverrides++}`;
overrideDecls.push(`override ${name} : ${el.type};`);
argOverrides.push(name);
@@ -150,7 +191,7 @@ const v = ${builtin}(${args.map(arg => arg.wgsl()).join(', ')});`
expectedResult,
code: `${enables}
${overrideDecls.join('\n')}
-var<private> v = ${builtin}(${callArgs.join(', ')});`,
+var<private> v ${optionalVarType} = ${builtin}(${callArgs.join(', ')});`,
constants,
reference: ['v'],
});
@@ -159,24 +200,92 @@ var<private> v = ${builtin}(${callArgs.join(', ')});`,
}
}
+/**
+ * Runs a validation test to check that evaluation of `binaryOp` either evaluates with or without
+ * error at shader creation time or pipeline creation time.
+ * @param t the ShaderValidationTest
+ * @param binaryOp the symbol of the binary operator
+ * @param expectedResult false if an error is expected, true if no error is expected
+ * @param leftStage the evaluation stage for the left argument
+ * @param left the left-hand side of the binary operation
+ * @param rightStage the evaluation stage for the right argument
+ * @param right the right-hand side of the binary operation
+ */
+export function validateConstOrOverrideBinaryOpEval(
+ t: ShaderValidationTest,
+ binaryOp: string,
+ expectedResult: boolean,
+ leftStage: ExecutionStage,
+ left: Value,
+ rightStage: ExecutionStage,
+ right: Value
+) {
+ const allArgs = [left, right];
+ const elTys = allArgs.map(arg => elementTypeOf(arg.type)!);
+ const enables = elTys.some(ty => ty === Type.f16) ? 'enable f16;' : '';
+
+ const codeLines = [enables];
+ const constants: Record<string, number> = {};
+ let numOverrides = 0;
+
+ function addOperand(name: string, stage: ExecutionStage, value: Value) {
+ switch (stage) {
+ case 'runtime':
+ assert(!isAbstractType(value.type));
+ codeLines.push(`var<private> ${name} = ${value.wgsl()};`);
+ return name;
+
+ case 'constant':
+ codeLines.push(`const ${name} = ${value.wgsl()};`);
+ return name;
+
+ case 'override': {
+ assert(!isAbstractType(value.type));
+ const argOverrides: string[] = [];
+ for (const el of scalarElementsOf(value)) {
+ const elName = `o${numOverrides++}`;
+ codeLines.push(`override ${elName} : ${el.type};`);
+ constants[elName] = Number(el.value);
+ argOverrides.push(elName);
+ }
+ return `${value.type}(${argOverrides.join(', ')})`;
+ }
+ }
+ }
+
+ const leftOperand = addOperand('left', leftStage, left);
+ const rightOperand = addOperand('right', rightStage, right);
+
+ if (leftStage === 'override' || rightStage === 'override') {
+ t.expectPipelineResult({
+ expectedResult,
+ code: codeLines.join('\n'),
+ constants,
+ reference: [`${leftOperand} ${binaryOp} ${rightOperand}`],
+ });
+ } else {
+ codeLines.push(`fn f() { _ = ${leftOperand} ${binaryOp} ${rightOperand}; }`);
+ t.expectCompileResult(expectedResult, codeLines.join('\n'));
+ }
+}
/** @returns a sweep of the representable values for element type of `type` */
-export function fullRangeForType(type: Type, count?: number) {
+export function fullRangeForType(type: Type, count?: number): readonly (number | bigint)[] {
if (count === undefined) {
count = 25;
}
- switch (elementType(type)?.kind) {
+ switch (scalarTypeOf(type)?.kind) {
case 'abstract-float':
- return fullF64Range({
+ return scalarF64Range({
pos_sub: Math.ceil((count * 1) / 5),
pos_norm: Math.ceil((count * 4) / 5),
});
case 'f32':
- return fullF32Range({
+ return scalarF32Range({
pos_sub: Math.ceil((count * 1) / 5),
pos_norm: Math.ceil((count * 4) / 5),
});
case 'f16':
- return fullF16Range({
+ return scalarF16Range({
pos_sub: Math.ceil((count * 1) / 5),
pos_norm: Math.ceil((count * 4) / 5),
});
@@ -186,6 +295,9 @@ export function fullRangeForType(type: Type, count?: number) {
);
case 'u32':
return linearRange(0, kValue.u32.max, count).map(f => Math.floor(f));
+ case 'abstract-int':
+ // Returned values are already ints, so don't need to be floored.
+ return linearRangeBigInt(kValue.i64.negative.min, kValue.i64.positive.max, count);
}
unreachable();
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cos.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cos.spec.ts
index b65593ccaa..361cb8ed99 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cos.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cos.spec.ts
@@ -6,18 +6,17 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinus3PiTo3Pi,
+ minusThreePiToThreePiRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -25,7 +24,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -39,10 +38,15 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(kMinus3PiTo3Pi, fullRangeForType(kValuesTypes[u.type])))
+ .expand('value', u =>
+ unique(
+ minusThreePiToThreePiRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -56,7 +60,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -70,8 +74,41 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(0)],
'constant'
);
});
+
+const kArgCases = {
+ good: '(1.1)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2args: '(1.0,2.0)',
+ // Bad value type for arg 0
+ bad_0i32: '(1i)',
+ bad_0u32: '(1u)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cosh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cosh.spec.ts
index 126fc19e7e..aeb5457675 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cosh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cosh.spec.ts
@@ -6,11 +6,9 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -24,7 +22,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -41,13 +39,17 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
- const expectedResult = isRepresentable(Math.cosh(t.params.value), elementType(type));
+ const expectedResult = isRepresentable(
+ Math.cosh(Number(t.params.value)),
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -57,22 +59,40 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
-g.test('integer_argument')
- .desc(
- `
-Validates that scalar and vector integer arguments are rejected by ${builtin}()
-`
- )
- .params(u => u.combine('type', keysOf(kIntegerArgumentTypes)))
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
.fn(t => {
- const type = kIntegerArgumentTypes[t.params.type];
- validateConstOrOverrideBuiltinEval(
- t,
- builtin,
- /* expectedResult */ type === TypeF32,
- [type.create(0)],
- 'constant'
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
);
});
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countLeadingZeros.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countLeadingZeros.spec.ts
new file mode 100644
index 0000000000..15791edc80
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countLeadingZeros.spec.ts
@@ -0,0 +1,198 @@
+const builtin = 'countLeadingZeros';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kFloatScalarsAndVectors,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConcreteIntegerScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type]))
+ )
+ .fn(t => {
+ const expectedResult = true; // countLeadingZeros() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+// u32 is included here to confirm that validation is failing due to a type issue and not something else.
+const kFloatTypes = objectsToRecord([Type.u32, ...kFloatScalarsAndVectors]);
+
+g.test('float_argument')
+ .desc(
+ `
+Validates that float arguments are rejected by ${builtin}()
+`
+ )
+ .params(u => u.combine('type', keysOf(kFloatTypes)))
+ .fn(t => {
+ const type = kFloatTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ type === Type.u32,
+ [type.create(0)],
+ 'constant'
+ );
+ });
+
+const kTests: {
+ readonly [name: string]: {
+ /** Arguments to pass to the builtin with parentheses. */
+ readonly args: string;
+ /** Should the test case pass. */
+ readonly pass: boolean;
+ /** Additional setup code in the function scope. */
+ readonly preamble?: string;
+ };
+} = {
+ valid: {
+ args: '(1u)',
+ pass: true,
+ },
+ // Number of arguments.
+ no_parens: {
+ args: '',
+ pass: false,
+ },
+ too_few_args: {
+ args: '()',
+ pass: false,
+ },
+ too_many_args: {
+ args: '(1u,2u)',
+ pass: false,
+ },
+ // Arguments types (only 1 argument for this builtin).
+ alias: {
+ args: '(u32_alias(1))',
+ pass: true,
+ },
+ bool: {
+ args: '(false)',
+ pass: false,
+ },
+ vec_bool: {
+ args: '(vec2<bool>(false,true))',
+ pass: false,
+ },
+ matrix: {
+ args: '(mat2x2(1,1,1,1))',
+ pass: false,
+ },
+ atomic: {
+ args: '(a)',
+ pass: false,
+ },
+ array: {
+ preamble: 'var arry: array<u32, 5>;',
+ args: '(arry)',
+ pass: false,
+ },
+ array_runtime: {
+ args: '(k.arry)',
+ pass: false,
+ },
+ struct: {
+ preamble: 'var x: A;',
+ args: '(x)',
+ pass: false,
+ },
+ enumerant: {
+ args: '(read_write)',
+ pass: false,
+ },
+ ptr: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(p)',
+ pass: false,
+ },
+ ptr_deref: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(*p)',
+ pass: true,
+ },
+ sampler: {
+ args: '(s)',
+ pass: false,
+ },
+ texture: {
+ args: '(t)',
+ pass: false,
+ },
+};
+
+g.test('arguments')
+ .desc(`Test compilation validation of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const test = kTests[t.params.test];
+ t.expectCompileResult(
+ test.pass,
+ `alias u32_alias = u32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: u32,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${test.preamble ? test.preamble : ''}
+ _ = ${builtin}${test.args};
+ return vec4<f32>(.4, .2, .3, .1);
+ }`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(1u); }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countOneBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countOneBits.spec.ts
new file mode 100644
index 0000000000..b083c7ca4b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countOneBits.spec.ts
@@ -0,0 +1,198 @@
+const builtin = 'countOneBits';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kFloatScalarsAndVectors,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConcreteIntegerScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type]))
+ )
+ .fn(t => {
+ const expectedResult = true; // countOneBits() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+// u32 is included here to confirm that validation is failing due to a type issue and not something else.
+const kFloatTypes = objectsToRecord([Type.u32, ...kFloatScalarsAndVectors]);
+
+g.test('float_argument')
+ .desc(
+ `
+Validates that float arguments are rejected by ${builtin}()
+`
+ )
+ .params(u => u.combine('type', keysOf(kFloatTypes)))
+ .fn(t => {
+ const type = kFloatTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ type === Type.u32,
+ [type.create(0)],
+ 'constant'
+ );
+ });
+
+const kTests: {
+ readonly [name: string]: {
+ /** Arguments to pass to the builtin with parentheses. */
+ readonly args: string;
+ /** Should the test case pass. */
+ readonly pass: boolean;
+ /** Additional setup code in the function scope. */
+ readonly preamble?: string;
+ };
+} = {
+ valid: {
+ args: '(1u)',
+ pass: true,
+ },
+ // Number of arguments.
+ no_parens: {
+ args: '',
+ pass: false,
+ },
+ too_few_args: {
+ args: '()',
+ pass: false,
+ },
+ too_many_args: {
+ args: '(1u,2u)',
+ pass: false,
+ },
+ // Arguments types (only 1 argument for this builtin).
+ alias: {
+ args: '(u32_alias(1))',
+ pass: true,
+ },
+ bool: {
+ args: '(false)',
+ pass: false,
+ },
+ vec_bool: {
+ args: '(vec2<bool>(false,true))',
+ pass: false,
+ },
+ matrix: {
+ args: '(mat2x2(1,1,1,1))',
+ pass: false,
+ },
+ atomic: {
+ args: '(a)',
+ pass: false,
+ },
+ array: {
+ preamble: 'var arry: array<u32, 5>;',
+ args: '(arry)',
+ pass: false,
+ },
+ array_runtime: {
+ args: '(k.arry)',
+ pass: false,
+ },
+ struct: {
+ preamble: 'var x: A;',
+ args: '(x)',
+ pass: false,
+ },
+ enumerant: {
+ args: '(read_write)',
+ pass: false,
+ },
+ ptr: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(p)',
+ pass: false,
+ },
+ ptr_deref: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(*p)',
+ pass: true,
+ },
+ sampler: {
+ args: '(s)',
+ pass: false,
+ },
+ texture: {
+ args: '(t)',
+ pass: false,
+ },
+};
+
+g.test('arguments')
+ .desc(`Test compilation validation of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const test = kTests[t.params.test];
+ t.expectCompileResult(
+ test.pass,
+ `alias u32_alias = u32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: u32,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${test.preamble ? test.preamble : ''}
+ _ = ${builtin}${test.args};
+ return vec4<f32>(.4, .2, .3, .1);
+ }`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(1u); }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countTrailingZeros.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countTrailingZeros.spec.ts
new file mode 100644
index 0000000000..800537d348
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/countTrailingZeros.spec.ts
@@ -0,0 +1,198 @@
+const builtin = 'countTrailingZeros';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kFloatScalarsAndVectors,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConcreteIntegerScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type]))
+ )
+ .fn(t => {
+ const expectedResult = true; // countTrailingZeros() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+// u32 is included here to confirm that validation is failing due to a type issue and not something else.
+const kFloatTypes = objectsToRecord([Type.u32, ...kFloatScalarsAndVectors]);
+
+g.test('float_argument')
+ .desc(
+ `
+Validates that float arguments are rejected by ${builtin}()
+`
+ )
+ .params(u => u.combine('type', keysOf(kFloatTypes)))
+ .fn(t => {
+ const type = kFloatTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ type === Type.u32,
+ [type.create(0)],
+ 'constant'
+ );
+ });
+
+const kTests: {
+ readonly [name: string]: {
+ /** Arguments to pass to the builtin with parentheses. */
+ readonly args: string;
+ /** Should the test case pass. */
+ readonly pass: boolean;
+ /** Additional setup code in the function scope. */
+ readonly preamble?: string;
+ };
+} = {
+ valid: {
+ args: '(1u)',
+ pass: true,
+ },
+ // Number of arguments.
+ no_parens: {
+ args: '',
+ pass: false,
+ },
+ too_few_args: {
+ args: '()',
+ pass: false,
+ },
+ too_many_args: {
+ args: '(1u,2u)',
+ pass: false,
+ },
+ // Arguments types (only 1 argument for this builtin).
+ alias: {
+ args: '(u32_alias(1))',
+ pass: true,
+ },
+ bool: {
+ args: '(false)',
+ pass: false,
+ },
+ vec_bool: {
+ args: '(vec2<bool>(false,true))',
+ pass: false,
+ },
+ matrix: {
+ args: '(mat2x2(1,1,1,1))',
+ pass: false,
+ },
+ atomic: {
+ args: '(a)',
+ pass: false,
+ },
+ array: {
+ preamble: 'var arry: array<u32, 5>;',
+ args: '(arry)',
+ pass: false,
+ },
+ array_runtime: {
+ args: '(k.arry)',
+ pass: false,
+ },
+ struct: {
+ preamble: 'var x: A;',
+ args: '(x)',
+ pass: false,
+ },
+ enumerant: {
+ args: '(read_write)',
+ pass: false,
+ },
+ ptr: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(p)',
+ pass: false,
+ },
+ ptr_deref: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(*p)',
+ pass: true,
+ },
+ sampler: {
+ args: '(s)',
+ pass: false,
+ },
+ texture: {
+ args: '(t)',
+ pass: false,
+ },
+};
+
+g.test('arguments')
+ .desc(`Test compilation validation of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const test = kTests[t.params.test];
+ t.expectCompileResult(
+ test.pass,
+ `alias u32_alias = u32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: u32,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${test.preamble ? test.preamble : ''}
+ _ = ${builtin}${test.args};
+ return vec4<f32>(.4, .2, .3, .1);
+ }`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(1u); }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cross.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cross.spec.ts
new file mode 100644
index 0000000000..35dacb65d8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/cross.spec.ts
@@ -0,0 +1,122 @@
+const builtin = 'cross';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConvertableToFloatVec3,
+ scalarTypeOf,
+ ScalarType,
+} from '../../../../../util/conversion.js';
+import { QuantizeFunc, quantizeToF16, quantizeToF32 } from '../../../../../util/math.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord(kConvertableToFloatVec3);
+
+function quantizeFunctionForScalarType(type: ScalarType): QuantizeFunc<number> {
+ switch (type) {
+ case Type.f32:
+ return quantizeToF32;
+ case Type.f16:
+ return quantizeToF16;
+ default:
+ return (v: number) => v;
+ }
+}
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('a', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ .expand('b', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValidArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let expectedResult = true;
+
+ const scalarType = scalarTypeOf(kValidArgumentTypes[t.params.type]);
+ const quantizeFn = quantizeFunctionForScalarType(scalarType);
+
+ // Should be invalid if the cross product calculations result in intermediate
+ // values that exceed the maximum representable float value for the given type.
+ const a = Number(t.params.a);
+ const b = Number(t.params.b);
+ const ab = quantizeFn(a * b);
+ if (ab === Infinity || ab === -Infinity) {
+ expectedResult = false;
+ }
+
+ const type = kValidArgumentTypes[t.params.type];
+
+ // Validates cross(vec3(a, a, a), vec3(b, b, b));
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.a), type.create(t.params.b)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(vec3(0), vec3(1))',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_1arg: '(1.0)',
+ bad_3arg: '(1.0, 2.0, 3.0)',
+ // Wrong vector size
+ bad_vec2: '(vec2(0), vec2(1))',
+ bad_vec4: '(vec4(0), vec4(1))',
+ // Bad value for arg 0
+ bad_0bool: '(false, vec3(1))',
+ bad_0array: '(array(1.1,2.2), vec3(1))',
+ bad_0struct: '(modf(2.2), vec3(1))',
+ // Bad value type for arg 1
+ bad_1bool: '(vec3(0), true)',
+ bad_1array: '(vec3(0), array(1.1,2.2))',
+ bad_1struct: '(vec3(0), modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/degrees.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/degrees.spec.ts
index 154455857d..058f6ffa6c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/degrees.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/degrees.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -24,7 +23,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -41,13 +40,17 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
- const expectedResult = isRepresentable((t.params.value * 180) / Math.PI, elementType(type));
+ const expectedResult = isRepresentable(
+ (Number(t.params.value) * 180) / Math.PI,
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -57,7 +60,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -71,8 +74,41 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(1)],
'constant'
);
});
+
+const kArgCases = {
+ good: '(1.1)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_too_few: '()',
+ bad_too_many: '(1.0,2.0)',
+ // Bad value type for arg 0
+ bad_0i32: '(1i)',
+ bad_0u32: '(1u)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/derivatives.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/derivatives.spec.ts
new file mode 100644
index 0000000000..54620ce179
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/derivatives.spec.ts
@@ -0,0 +1,129 @@
+export const description = `
+Validation tests for derivative builtins.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConcreteF16ScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kDerivativeBuiltins = [
+ 'dpdx',
+ 'dpdxCoarse',
+ 'dpdxFine',
+ 'dpdy',
+ 'dpdyCoarse',
+ 'dpdyFine',
+ 'fwidth',
+ 'fwidthCoarse',
+ 'fwidthFine',
+];
+
+const kEntryPoints = {
+ none: { supportsDerivative: true, code: `` },
+ fragment: {
+ supportsDerivative: true,
+ code: `@fragment
+fn main() {
+ foo();
+}`,
+ },
+ vertex: {
+ supportsDerivative: false,
+ code: `@vertex
+fn main() -> @builtin(position) vec4f {
+ foo();
+ return vec4f();
+}`,
+ },
+ compute: {
+ supportsDerivative: false,
+ code: `@compute @workgroup_size(1)
+fn main() {
+ foo();
+}`,
+ },
+ fragment_and_compute: {
+ supportsDerivative: false,
+ code: `@fragment
+fn main1() {
+ foo();
+}
+
+@compute @workgroup_size(1)
+fn main2() {
+ foo();
+}
+`,
+ },
+ compute_without_call: {
+ supportsDerivative: true,
+ code: `@compute @workgroup_size(1)
+fn main() {
+}
+`,
+ },
+};
+
+g.test('only_in_fragment')
+ .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
+ .desc(
+ `
+Derivative functions must only be used in the fragment shader stage.
+`
+ )
+ .params(u =>
+ u.combine('entry_point', keysOf(kEntryPoints)).combine('call', ['bar', ...kDerivativeBuiltins])
+ )
+ .fn(t => {
+ const config = kEntryPoints[t.params.entry_point];
+ const code = `
+${config.code}
+fn bar(f : f32) -> f32 { return f; }
+
+fn foo() {
+ _ = ${t.params.call}(1.0);
+}`;
+ t.expectCompileResult(t.params.call === 'bar' || config.supportsDerivative, code);
+ });
+
+// The list of invalid argument types to test, with an f32 control case.
+const kArgumentTypes = objectsToRecord([
+ Type.f32,
+ ...kConcreteIntegerScalarsAndVectors,
+ ...kConcreteF16ScalarsAndVectors,
+ Type.mat2x2f,
+]);
+
+g.test('invalid_argument_types')
+ .specURL('https://www.w3.org/TR/WGSL/#derivative-builtin-functions')
+ .desc(
+ `
+Derivative builtins only accept f32 scalar and vector types.
+`
+ )
+ .params(u =>
+ u.combine('type', keysOf(kArgumentTypes)).combine('call', ['', ...kDerivativeBuiltins])
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kArgumentTypes[t.params.type];
+ const code = `
+${scalarTypeOf(kArgumentTypes[t.params.type]) === Type.f16 ? 'enable f16;' : ''}
+
+fn foo() {
+ let x: ${type.toString()} = ${t.params.call}(${type.create(1).wgsl()});
+}`;
+ t.expectCompileResult(kArgumentTypes[t.params.type] === Type.f32 || t.params.call === '', code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/determinant.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/determinant.spec.ts
new file mode 100644
index 0000000000..1974e7ef99
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/determinant.spec.ts
@@ -0,0 +1,95 @@
+const builtin = 'determinant';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// Generate a dictionary mapping each matrix type variation (columns,rows,
+// floating point type) to a nontrivial matrix value of that type.
+const kMatrixCases = ([2, 3, 4] as const)
+ .flatMap(cols =>
+ ([2, 3, 4] as const).flatMap(rows =>
+ (['abstract-int', 'abstract-float', 'f32', 'f16'] as const).map(type => ({
+ [`mat${cols}x${rows}_${type}`]: (() => {
+ const suffix = (() => {
+ switch (type) {
+ case 'abstract-int':
+ return '';
+ case 'abstract-float':
+ return '.0';
+ case 'f32':
+ return 'f';
+ case 'f16':
+ return 'h';
+ }
+ })();
+ return `(mat${cols}x${rows}(${[...Array(cols * rows).keys()]
+ .map(e => `${e}${suffix}`)
+ .join(', ')}))`;
+ })(),
+ }))
+ )
+ )
+ .reduce((a, b) => ({ ...a, ...b }), {});
+
+g.test('matrix_args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped matrices`)
+ .params(u =>
+ u
+ .combine('cols', [2, 3, 4] as const)
+ .combine('rows', [2, 3, 4] as const)
+ .combine('type', ['abstract-int', 'abstract-float', 'f32', 'f16'] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const cols = t.params.cols;
+ const rows = t.params.rows;
+ const type = t.params.type;
+ const arg = kMatrixCases[`mat${cols}x${rows}_${type}`];
+ t.expectCompileResult(
+ cols === rows,
+ t.wrapInEntryPoint(`const c = ${builtin}${arg};`, type === 'f16' ? ['f16'] : [])
+ );
+ });
+
+const kArgCases = {
+ good: '(mat2x2(0.0, 2.0, 3.0, 4.0))', // Included to check test implementation
+ bad_no_parens: '',
+ // Bad number of args
+ bad_too_few: '()',
+ bad_too_many: '(mat2x2(0.0, 2.0, 3.0, 4.0), mat2x2(0.0, 2.0, 3.0, 4.0))',
+ // Bad value type for arg 0
+ bad_0i32: '(1i)',
+ bad_0u32: '(1u)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/distance.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/distance.spec.ts
new file mode 100644
index 0000000000..e41a8dd31c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/distance.spec.ts
@@ -0,0 +1,149 @@
+const builtin = 'distance';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
+ ScalarType,
+} from '../../../../../util/conversion.js';
+import { QuantizeFunc, quantizeToF16, quantizeToF32 } from '../../../../../util/math.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+function quantizeFunctionForScalarType(type: ScalarType): QuantizeFunc<number> {
+ switch (type) {
+ case Type.f32:
+ return quantizeToF32;
+ case Type.f16:
+ return quantizeToF16;
+ default:
+ return (v: number) => v;
+ }
+}
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('a', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ .expand('b', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValidArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let expectedResult = true;
+
+ const scalarType = scalarTypeOf(kValidArgumentTypes[t.params.type]);
+ const quantizeFn = quantizeFunctionForScalarType(scalarType);
+
+ // Distance equation: length(a - b)
+ // Should be invalid if the calculations result in intermediate values that
+ // exceed the maximum representable float value for the given type.
+ const a = Number(t.params.a);
+ const b = Number(t.params.b);
+ const ab = quantizeFn(a - b);
+
+ if (!Number.isFinite(ab)) {
+ expectedResult = false;
+ }
+
+ // Only calculates the full length if the type is a vector. Otherwise abs(a-b) is used.
+ if (kValidArgumentTypes[t.params.type].width > 1) {
+ const ab2 = quantizeFn(ab * ab);
+ const sqrLen = quantizeFn(ab2 * kValidArgumentTypes[t.params.type].width);
+ // Square root does not need to be calculated because it can never fail if
+ // the previous results are finite.
+
+ if (!Number.isFinite(ab2) || !Number.isFinite(sqrLen)) {
+ expectedResult = false;
+ }
+ }
+
+ const type = kValidArgumentTypes[t.params.type];
+
+ // Validates distance(vecN(a), vecN(b)) or distance(a, b);
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.a), type.create(t.params.b)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(vec3(0), vec3(1))',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_1arg: '(vec3(0))',
+ bad_3arg: '(vec3(0), vec3(1), vec3(2))',
+ // Bad value for arg 0
+ bad_0bool: '(false, vec3(1))',
+ bad_0array: '(array(1.1,2.2), vec3(1))',
+ bad_0struct: '(modf(2.2), vec3(1))',
+ bad_0int: '(0i, vec3(1))',
+ bad_0vec2i: '(vec2i(), vec3(1))',
+ bad_0vec3i: '(vec3i(), vec3(1))',
+ bad_0vec4i: '(vec4i(), vec3(1))',
+ bad_0uint: '(0u, vec3(1))',
+ bad_0vec2u: '(vec2u(), vec3(1))',
+ bad_0vec3u: '(vec3u(), vec3(1))',
+ bad_0vec4u: '(vec4u(), vec3(1))',
+ // Bad value type for arg 1
+ bad_1bool: '(vec3(0), true)',
+ bad_1array: '(vec3(0), array(1.1,2.2))',
+ bad_1struct: '(vec3(0), modf(2.2))',
+ bad_1int: '(vec3(0), 0i)',
+ bad_1vec2i: '(vec3(0), vec2i())',
+ bad_1vec3i: '(vec3(0), vec3i())',
+ bad_1vec4i: '(vec3(0), vec4i())',
+ bad_1uint: '(vec3(0), 0u)',
+ bad_1vec2u: '(vec3(0), vec2u())',
+ bad_1vec3u: '(vec3(0), vec3u())',
+ bad_1vec4u: '(vec3(0), vec4u())',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/dot4I8Packed.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/dot4I8Packed.spec.ts
new file mode 100644
index 0000000000..c079e08cb1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/dot4I8Packed.spec.ts
@@ -0,0 +1,66 @@
+export const description = `Validate dot4I8Packed`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kFeature = 'packed_4x8_integer_dot_product';
+const kFn = 'dot4I8Packed';
+const kArgCases = {
+ good: '(1u,2u)',
+ bad_0args: '()',
+ bad_1args: '(1u)',
+ bad_3args: '(1u,2u,3u)',
+ bad_0i32: '(1i,2u)',
+ bad_0f32: '(1f,2u)',
+ bad_0bool: '(false,2u)',
+ bad_0vec2u: '(vec2u(),2u)',
+ bad_1i32: '(1u,2i)',
+ bad_1f32: '(1u,2f)',
+ bad_1bool: '(1u,true)',
+ bad_1vec2u: '(1u,vec2u())',
+ bad_bool_bool: '(false,true)',
+ bad_bool2_bool2: '(vec2<bool>(),vec2(false,true))',
+ bad_0array: '(array(1))',
+ bad_0struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('unsupported')
+ .desc(`Test absence of ${kFn} when ${kFeature} is not supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(false, code);
+ });
+
+g.test('supported')
+ .desc(`Test presence of ${kFn} when ${kFeature} is supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(true, code);
+ });
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ t.expectCompileResult(t.params.arg === 'good', `const c = ${kFn}${kArgCases[t.params.arg]};`);
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/dot4U8Packed.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/dot4U8Packed.spec.ts
new file mode 100644
index 0000000000..bd9356777e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/dot4U8Packed.spec.ts
@@ -0,0 +1,66 @@
+export const description = `Validate dot4U8Packed`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kFeature = 'packed_4x8_integer_dot_product';
+const kFn = 'dot4U8Packed';
+const kArgCases = {
+ good: '(1u,2u)',
+ bad_0args: '()',
+ bad_1args: '(1u)',
+ bad_3args: '(1u,2u,3u)',
+ bad_0i32: '(1i,2u)',
+ bad_0f32: '(1f,2u)',
+ bad_0bool: '(false,2u)',
+ bad_0vec2u: '(vec2u(),2u)',
+ bad_1i32: '(1u,2i)',
+ bad_1f32: '(1u,2f)',
+ bad_1bool: '(1u,true)',
+ bad_1vec2u: '(1u,vec2u())',
+ bad_bool_bool: '(false,true)',
+ bad_bool2_bool2: '(vec2<bool>(),vec2(false,true))',
+ bad_0array: '(array(1))',
+ bad_0struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('unsupported')
+ .desc(`Test absence of ${kFn} when ${kFeature} is not supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(false, code);
+ });
+
+g.test('supported')
+ .desc(`Test presence of ${kFn} when ${kFeature} is supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(true, code);
+ });
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ t.expectCompileResult(t.params.arg === 'good', `const c = ${kFn}${kArgCases[t.params.arg]};`);
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp.spec.ts
index 244e91f2ae..f0a9b217b1 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp.spec.ts
@@ -7,24 +7,52 @@ import { makeTestGroup } from '../../../../../../common/framework/test_group.js'
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import { kValue } from '../../../../../util/constants.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
kConstantAndOverrideStages,
+ rangeForType,
stageSupportsType,
validateConstOrOverrideBuiltinEval,
} from './const_override_validation.js';
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+const valueForType = rangeForType(
+ [
+ -1e2,
+ -1e3,
+ -4,
+ -3,
+ -2,
+ -1,
+ -1e-1,
+ -1e-2,
+ -1e-3,
+ 0,
+ 1e-3,
+ 1e-2,
+ 1e-1,
+ 1,
+ 2,
+ 3,
+ 4,
+ 1e2,
+ 1e3,
+ Math.log2(kValue.f16.positive.max) - 0.1,
+ Math.log2(kValue.f16.positive.max) + 0.1,
+ Math.log2(kValue.f32.positive.max) - 0.1,
+ Math.log2(kValue.f32.positive.max) + 0.1,
+ ],
+ [-100n, -1000n, -4n, -3n, -2n, -1n, 0n, 1n, 2n, 3n, 4n, 100n, 1000n]
+);
g.test('values')
.desc(
@@ -38,40 +66,20 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .combine('value', [
- -1e2,
- -1e3,
- -4,
- -3,
- -2,
- -1,
- -1e-1,
- -1e-2,
- -1e-3,
- 0,
- 1e-3,
- 1e-2,
- 1e-1,
- 1,
- 2,
- 3,
- 4,
- 1e2,
- 1e3,
- Math.log2(kValue.f16.positive.max) - 0.1,
- Math.log2(kValue.f16.positive.max) + 0.1,
- Math.log2(kValue.f32.positive.max) - 0.1,
- Math.log2(kValue.f32.positive.max) + 0.1,
- ])
+ .expand('value', u => valueForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
- const expectedResult = isRepresentable(Math.exp(t.params.value), elementType(type));
+ const expectedResult = isRepresentable(
+ Math.exp(Number(t.params.value)),
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -81,22 +89,40 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
-g.test('integer_argument')
- .desc(
- `
-Validates that scalar and vector integer arguments are rejected by ${builtin}()
-`
- )
- .params(u => u.combine('type', keysOf(kIntegerArgumentTypes)))
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
.fn(t => {
- const type = kIntegerArgumentTypes[t.params.type];
- validateConstOrOverrideBuiltinEval(
- t,
- builtin,
- /* expectedResult */ type === TypeF32,
- [type.create(0)],
- 'constant'
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
);
});
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp2.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp2.spec.ts
index 9addbc076b..0cf3d03542 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp2.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/exp2.spec.ts
@@ -7,24 +7,52 @@ import { makeTestGroup } from '../../../../../../common/framework/test_group.js'
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import { kValue } from '../../../../../util/constants.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
kConstantAndOverrideStages,
+ rangeForType,
stageSupportsType,
validateConstOrOverrideBuiltinEval,
} from './const_override_validation.js';
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+const valueForType = rangeForType(
+ [
+ -1e2,
+ -1e3,
+ -4,
+ -3,
+ -2,
+ -1,
+ -1e-1,
+ -1e-2,
+ -1e-3,
+ 0,
+ 1e-3,
+ 1e-2,
+ 1e-1,
+ 1,
+ 2,
+ 3,
+ 4,
+ 1e2,
+ 1e3,
+ Math.log2(kValue.f16.positive.max) - 0.1,
+ Math.log2(kValue.f16.positive.max) + 0.1,
+ Math.log2(kValue.f32.positive.max) - 0.1,
+ Math.log2(kValue.f32.positive.max) + 0.1,
+ ],
+ [-100n, -1000n, -4n, -3n, -2n, -1n, 0n, 1n, 2n, 3n, 4n, 100n, 1000n]
+);
g.test('values')
.desc(
@@ -38,40 +66,20 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .combine('value', [
- -1e2,
- -1e3,
- -4,
- -3,
- -2,
- -1,
- -1e-1,
- -1e-2,
- -1e-3,
- 0,
- 1e-3,
- 1e-2,
- 1e-1,
- 1,
- 2,
- 3,
- 4,
- 1e2,
- 1e3,
- Math.log2(kValue.f16.positive.max) - 0.1,
- Math.log2(kValue.f16.positive.max) + 0.1,
- Math.log2(kValue.f32.positive.max) - 0.1,
- Math.log2(kValue.f32.positive.max) + 0.1,
- ])
+ .expand('value', u => valueForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
- const expectedResult = isRepresentable(Math.pow(2, t.params.value), elementType(type));
+ const expectedResult = isRepresentable(
+ Math.pow(2, Number(t.params.value)),
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -81,22 +89,40 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
-g.test('integer_argument')
- .desc(
- `
-Validates that scalar and vector integer arguments are rejected by ${builtin}()
-`
- )
- .params(u => u.combine('type', keysOf(kIntegerArgumentTypes)))
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
.fn(t => {
- const type = kIntegerArgumentTypes[t.params.type];
- validateConstOrOverrideBuiltinEval(
- t,
- builtin,
- /* expectedResult */ type === TypeF32,
- [type.create(0)],
- 'constant'
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
);
});
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/extractBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/extractBits.spec.ts
new file mode 100644
index 0000000000..921e157408
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/extractBits.spec.ts
@@ -0,0 +1,218 @@
+const builtin = 'extractBits';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kFloatScalarsAndVectors,
+ u32,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConcreteIntegerScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors on valid inputs
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type]))
+ .combineWithParams([
+ { offset: 0, count: 0 },
+ { offset: 0, count: 31 },
+ { offset: 0, count: 32 },
+ { offset: 4, count: 0 },
+ { offset: 4, count: 27 },
+ { offset: 4, count: 28 },
+ { offset: 16, count: 0 },
+ { offset: 16, count: 15 },
+ { offset: 16, count: 16 },
+ { offset: 32, count: 0 },
+ ] as const)
+ )
+ .fn(t => {
+ const expectedResult = true; // extractBits() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [
+ kValuesTypes[t.params.type].create(t.params.value),
+ u32(t.params.offset),
+ u32(t.params.count),
+ ],
+ t.params.stage
+ );
+ });
+
+g.test('count_offset')
+ .desc(
+ `
+Validates that count and offset must be smaller than the size of the primitive.
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .beginSubcases()
+ .combineWithParams([
+ // offset + count < 32
+ { offset: 0, count: 31 },
+ { offset: 1, count: 30 },
+ { offset: 31, count: 0 },
+ { offset: 30, count: 1 },
+ // offset + count == 32
+ { offset: 0, count: 32 },
+ { offset: 1, count: 31 },
+ { offset: 16, count: 16 },
+ { offset: 31, count: 1 },
+ { offset: 32, count: 0 },
+ // offset + count > 32
+ { offset: 2, count: 31 },
+ { offset: 31, count: 2 },
+ // offset > 32
+ { offset: 33, count: 0 },
+ { offset: 33, count: 1 },
+ // count > 32
+ { offset: 0, count: 33 },
+ { offset: 1, count: 33 },
+ ] as const)
+ )
+ .fn(t => {
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ t.params.offset + t.params.count <= 32,
+ [u32(1), u32(t.params.offset), u32(t.params.count)],
+ t.params.stage
+ );
+ });
+
+interface Argument {
+ /** Argument as a string. */
+ readonly arg: string;
+ /** Is this a valid argument type. Note that all args must be valid for the call to be valid. */
+ readonly pass: boolean;
+ /** Additional setup code necessary for this arg in the function scope. */
+ readonly preamble?: string;
+}
+
+function typesToArguments(types: readonly Type[], pass: boolean): Record<string, Argument> {
+ return types.reduce(
+ (res, type) => ({
+ ...res,
+ [type.toString()]: { arg: type.create(0).wgsl(), pass },
+ }),
+ {}
+ );
+}
+
+// u32 is included here to confirm that validation is failing due to a type issue and not something else.
+const kInputArgTypes: { readonly [name: string]: Argument } = {
+ ...typesToArguments([Type.u32], true),
+ ...typesToArguments([...kFloatScalarsAndVectors, Type.bool, Type.mat2x2f], false),
+ alias: { arg: 'u32_alias(1)', pass: true },
+ vec_bool: { arg: 'vec2<bool>(false,true)', pass: false },
+ atomic: { arg: 'a', pass: false },
+ array: {
+ preamble: 'var arry: array<u32, 5>;',
+ arg: 'arry',
+ pass: false,
+ },
+ array_runtime: { arg: 'k.arry', pass: false },
+ struct: {
+ preamble: 'var x: A;',
+ arg: 'x',
+ pass: false,
+ },
+ enumerant: { arg: 'read_write', pass: false },
+ ptr: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ arg: 'p',
+ pass: false,
+ },
+ ptr_deref: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ arg: '*p',
+ pass: true,
+ },
+ sampler: { arg: 's', pass: false },
+ texture: { arg: 't', pass: false },
+};
+
+g.test('typed_arguments')
+ .desc(
+ `
+Test compilation validation of ${builtin} with variously typed arguments
+ - For failing input types, just use the same type for offset and count to reduce combinations.
+`
+ )
+ .params(u =>
+ u
+ .combine('input', keysOf(kInputArgTypes))
+ .beginSubcases()
+ .combine('offset', keysOf(kInputArgTypes))
+ .expand('count', u => (kInputArgTypes[u.input].pass ? keysOf(kInputArgTypes) : [u.offset]))
+ )
+ .fn(t => {
+ const input = kInputArgTypes[t.params.input];
+ const offset = kInputArgTypes[t.params.offset];
+ const count = kInputArgTypes[t.params.count];
+ t.expectCompileResult(
+ input.pass && offset.pass && count.pass,
+ `alias u32_alias = u32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: u32,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${input.preamble ? input.preamble : ''}
+ ${offset.preamble && offset !== input ? offset.preamble : ''}
+ ${count.preamble && count !== input && count !== offset ? count.preamble : ''}
+ _ = ${builtin}(${input.arg},${offset.arg},${count.arg});
+ return vec4<f32>(.4, .2, .3, .1);
+ }`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(1u,0u,1u); }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/faceForward.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/faceForward.spec.ts
new file mode 100644
index 0000000000..4a87b5cacc
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/faceForward.spec.ts
@@ -0,0 +1,152 @@
+const builtin = 'faceForward';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConvertableToFloatVectors,
+ scalarTypeOf,
+ ScalarType,
+} from '../../../../../util/conversion.js';
+import { QuantizeFunc, quantizeToF16, quantizeToF32 } from '../../../../../util/math.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord(kConvertableToFloatVectors);
+
+function quantizeFunctionForScalarType(type: ScalarType): QuantizeFunc<number> {
+ switch (type) {
+ case Type.f32:
+ return quantizeToF32;
+ case Type.f16:
+ return quantizeToF16;
+ default:
+ return (v: number) => v;
+ }
+}
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('a', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ .expand('b', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ .expand('c', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValidArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let expectedResult = true;
+
+ const scalarType = scalarTypeOf(kValidArgumentTypes[t.params.type]);
+ const quantizeFn = quantizeFunctionForScalarType(scalarType);
+
+ // Face Forward equation: dot(b, c) < 0 ? -a : a
+ // Should be invalid if the calculations result in intermediate values that
+ // exceed the maximum representable float value for the given type.
+ const b = Number(t.params.b);
+ const c = Number(t.params.c);
+ const bc = quantizeFn(b * c);
+ const dp = quantizeFn(bc * kValidArgumentTypes[t.params.type].width);
+
+ if (!Number.isFinite(dp)) {
+ expectedResult = false;
+ }
+
+ const type = kValidArgumentTypes[t.params.type];
+
+ // Validates faceForward(vecN(a), vecN(b), vecN(c));
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.a), type.create(t.params.b), type.create(t.params.c)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(vec3(0), vec3(1), vec3(0.5))',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_1arg: '(vec3(0))',
+ bad_2arg: '(vec3(0), vec3(1))',
+ bad_4arg: '(vec3(0), vec3(1), vec3(0.5), vec3(3))',
+ // Bad value for arg 0
+ bad_0bool: '(false, vec3(1), vec3(0.5))',
+ bad_0array: '(array(1.1,2.2), vec3(1), vec3(0.5))',
+ bad_0struct: '(modf(2.2), vec3(1), vec3(0.5))',
+ bad_0int: '(1i, vec3(1), vec3(0.5))',
+ bad_0uint: '(1u, vec3(1), vec3(0.5))',
+ bad_0vec2i: '(vec2i(0), vec2(1), vec2(0.5))',
+ bad_0vec3i: '(vec3i(0), vec3(1), vec3(0.5))',
+ bad_0vec4i: '(vec4i(0), vec4(1), vec4(0.5))',
+ bad_0vec2u: '(vec2u(0), vec2(1), vec2(0.5))',
+ bad_0vec3u: '(vec3u(0), vec3(1), vec3(0.5))',
+ bad_0vec4u: '(vec4u(0), vec4(1), vec4(0.5))',
+ // Bad value type for arg 1
+ bad_1bool: '(vec3(0), true, vec3(0.5))',
+ bad_1array: '(vec3(0), array(1.1,2.2), vec3(0.5))',
+ bad_1struct: '(vec3(0), modf(2.2), vec3(0.5))',
+ bad_1int: '(vec3(0), 1i, vec3(0.5))',
+ bad_1uint: '(vec3(0), 1u, vec3(0.5))',
+ bad_1vec2i: '(vec2(1), vec2i(1), vec2(0.5))',
+ bad_1vec3i: '(vec3(1), vec3i(1), vec3(0.5))',
+ bad_1vec4i: '(vec4(1), vec4i(1), vec4(0.5))',
+ bad_1vec2u: '(vec2(1), vec2u(1), vec2(0.5))',
+ bad_1vec3u: '(vec3(1), vec3u(1), vec3(0.5))',
+ bad_1vec4u: '(vec4(1), vec4u(1), vec4(0.5))',
+ // Bad value type for arg 2
+ bad_2bool: '(vec3(0), vec3(1), true)',
+ bad_2array: '(vec3(0), vec3(1), array(1.1,2.2))',
+ bad_2struct: '(vec3(0), vec3(1), modf(2.2))',
+ bad_2int: '(vec3(0), vec3(1), 1i)',
+ bad_2uint: '(vec3(0), vec3(1), 1u)',
+ bad_2vec2i: '(vec2(1), vec2(1), vec2i(1))',
+ bad_2vec3i: '(vec3(1), vec3(1), vec3i(1))',
+ bad_2vec4i: '(vec4(1), vec4(1), vec4i(1))',
+ bad_2vec2u: '(vec2(1), vec2(1), vec2u(1))',
+ bad_2vec3u: '(vec3(1), vec3(1), vec3u(1))',
+ bad_2vec4u: '(vec4(1), vec4(1), vec4u(1))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/firstLeadingBit.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/firstLeadingBit.spec.ts
new file mode 100644
index 0000000000..4aeb7e8bd2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/firstLeadingBit.spec.ts
@@ -0,0 +1,198 @@
+const builtin = 'firstLeadingBit';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kFloatScalarsAndVectors,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConcreteIntegerScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type]))
+ )
+ .fn(t => {
+ const expectedResult = true; // firstLeadingBit() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+// u32 is included here to confirm that validation is failing due to a type issue and not something else.
+const kFloatTypes = objectsToRecord([Type.u32, ...kFloatScalarsAndVectors]);
+
+g.test('float_argument')
+ .desc(
+ `
+Validates that float arguments are rejected by ${builtin}()
+`
+ )
+ .params(u => u.combine('type', keysOf(kFloatTypes)))
+ .fn(t => {
+ const type = kFloatTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ type === Type.u32,
+ [type.create(0)],
+ 'constant'
+ );
+ });
+
+const kTests: {
+ readonly [name: string]: {
+ /** Arguments to pass to the builtin with parentheses. */
+ readonly args: string;
+ /** Should the test case pass. */
+ readonly pass: boolean;
+ /** Additional setup code in the function scope. */
+ readonly preamble?: string;
+ };
+} = {
+ valid: {
+ args: '(1u)',
+ pass: true,
+ },
+ // Number of arguments.
+ no_parens: {
+ args: '',
+ pass: false,
+ },
+ too_few_args: {
+ args: '()',
+ pass: false,
+ },
+ too_many_args: {
+ args: '(1u,2u)',
+ pass: false,
+ },
+ // Arguments types (only 1 argument for this builtin).
+ alias: {
+ args: '(u32_alias(1))',
+ pass: true,
+ },
+ bool: {
+ args: '(false)',
+ pass: false,
+ },
+ vec_bool: {
+ args: '(vec2<bool>(false,true))',
+ pass: false,
+ },
+ matrix: {
+ args: '(mat2x2(1,1,1,1))',
+ pass: false,
+ },
+ atomic: {
+ args: '(a)',
+ pass: false,
+ },
+ array: {
+ preamble: 'var arry: array<u32, 5>;',
+ args: '(arry)',
+ pass: false,
+ },
+ array_runtime: {
+ args: '(k.arry)',
+ pass: false,
+ },
+ struct: {
+ preamble: 'var x: A;',
+ args: '(x)',
+ pass: false,
+ },
+ enumerant: {
+ args: '(read_write)',
+ pass: false,
+ },
+ ptr: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(p)',
+ pass: false,
+ },
+ ptr_deref: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(*p)',
+ pass: true,
+ },
+ sampler: {
+ args: '(s)',
+ pass: false,
+ },
+ texture: {
+ args: '(t)',
+ pass: false,
+ },
+};
+
+g.test('arguments')
+ .desc(`Test compilation validation of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const test = kTests[t.params.test];
+ t.expectCompileResult(
+ test.pass,
+ `alias u32_alias = u32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: u32,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${test.preamble ? test.preamble : ''}
+ _ = ${builtin}${test.args};
+ return vec4<f32>(.4, .2, .3, .1);
+ }`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(1u); }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/firstTrailingBit.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/firstTrailingBit.spec.ts
new file mode 100644
index 0000000000..897a213fb8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/firstTrailingBit.spec.ts
@@ -0,0 +1,198 @@
+const builtin = 'firstTrailingBit';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kFloatScalarsAndVectors,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConcreteIntegerScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type]))
+ )
+ .fn(t => {
+ const expectedResult = true; // firstTrailingBit() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+// u32 is included here to confirm that validation is failing due to a type issue and not something else.
+const kFloatTypes = objectsToRecord([Type.u32, ...kFloatScalarsAndVectors]);
+
+g.test('float_argument')
+ .desc(
+ `
+Validates that float arguments are rejected by ${builtin}()
+`
+ )
+ .params(u => u.combine('type', keysOf(kFloatTypes)))
+ .fn(t => {
+ const type = kFloatTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ type === Type.u32,
+ [type.create(0)],
+ 'constant'
+ );
+ });
+
+const kTests: {
+ readonly [name: string]: {
+ /** Arguments to pass to the builtin with parentheses. */
+ readonly args: string;
+ /** Should the test case pass. */
+ readonly pass: boolean;
+ /** Additional setup code in the function scope. */
+ readonly preamble?: string;
+ };
+} = {
+ valid: {
+ args: '(1u)',
+ pass: true,
+ },
+ // Number of arguments.
+ no_parens: {
+ args: '',
+ pass: false,
+ },
+ too_few_args: {
+ args: '()',
+ pass: false,
+ },
+ too_many_args: {
+ args: '(1u,2u)',
+ pass: false,
+ },
+ // Arguments types (only 1 argument for this builtin).
+ alias: {
+ args: '(u32_alias(1))',
+ pass: true,
+ },
+ bool: {
+ args: '(false)',
+ pass: false,
+ },
+ vec_bool: {
+ args: '(vec2<bool>(false,true))',
+ pass: false,
+ },
+ matrix: {
+ args: '(mat2x2(1,1,1,1))',
+ pass: false,
+ },
+ atomic: {
+ args: '(a)',
+ pass: false,
+ },
+ array: {
+ preamble: 'var arry: array<u32, 5>;',
+ args: '(arry)',
+ pass: false,
+ },
+ array_runtime: {
+ args: '(k.arry)',
+ pass: false,
+ },
+ struct: {
+ preamble: 'var x: A;',
+ args: '(x)',
+ pass: false,
+ },
+ enumerant: {
+ args: '(read_write)',
+ pass: false,
+ },
+ ptr: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(p)',
+ pass: false,
+ },
+ ptr_deref: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(*p)',
+ pass: true,
+ },
+ sampler: {
+ args: '(s)',
+ pass: false,
+ },
+ texture: {
+ args: '(t)',
+ pass: false,
+ },
+};
+
+g.test('arguments')
+ .desc(`Test compilation validation of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const test = kTests[t.params.test];
+ t.expectCompileResult(
+ test.pass,
+ `alias u32_alias = u32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: u32,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${test.preamble ? test.preamble : ''}
+ _ = ${builtin}${test.args};
+ return vec4<f32>(.4, .2, .3, .1);
+ }`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(1u); }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/floor.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/floor.spec.ts
new file mode 100644
index 0000000000..ca699c6414
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/floor.spec.ts
@@ -0,0 +1,108 @@
+const builtin = 'floor';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type]))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const expectedResult = true; // floor() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
+
+g.test('integer_argument')
+ .desc(
+ `
+Validates that scalar and vector integer arguments are rejected by ${builtin}()
+`
+ )
+ .params(u => u.combine('type', keysOf(kIntegerArgumentTypes)))
+ .fn(t => {
+ const type = kIntegerArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ type === Type.f32,
+ [type.create(0)],
+ 'constant'
+ );
+ });
+
+const kArgCases = {
+ good: '(1.1)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2args: '(1.0,2.0)',
+ // Bad value type for arg 0
+ bad_0i32: '(1i)',
+ bad_0u32: '(1u)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/fract.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/fract.spec.ts
new file mode 100644
index 0000000000..c930c06ac4
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/fract.spec.ts
@@ -0,0 +1,94 @@
+const builtin = 'fract';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() error on invalid inputs.
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValidArgumentTypes[u.type]))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValidArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const expectedResult = true;
+
+ const type = kValidArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/frexp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/frexp.spec.ts
new file mode 100644
index 0000000000..16eb29f9ea
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/frexp.spec.ts
@@ -0,0 +1,94 @@
+const builtin = 'frexp';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() error on invalid inputs.
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValidArgumentTypes[u.type]))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValidArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const expectedResult = true;
+
+ const type = kValidArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/insertBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/insertBits.spec.ts
new file mode 100644
index 0000000000..17065e33ae
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/insertBits.spec.ts
@@ -0,0 +1,241 @@
+const builtin = 'insertBits';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kFloatScalarsAndVectors,
+ u32,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConcreteIntegerScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors on valid inputs
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type], 5))
+ .expand('newbits', u => fullRangeForType(kValuesTypes[u.type], 5))
+ .combineWithParams([
+ { offset: 0, count: 0 },
+ { offset: 0, count: 31 },
+ { offset: 0, count: 32 },
+ { offset: 4, count: 0 },
+ { offset: 4, count: 27 },
+ { offset: 4, count: 28 },
+ { offset: 16, count: 0 },
+ { offset: 16, count: 15 },
+ { offset: 16, count: 16 },
+ { offset: 32, count: 0 },
+ ] as const)
+ )
+ .fn(t => {
+ const expectedResult = true; // insertBits() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [
+ kValuesTypes[t.params.type].create(t.params.value),
+ kValuesTypes[t.params.type].create(t.params.newbits),
+ u32(t.params.offset),
+ u32(t.params.count),
+ ],
+ t.params.stage
+ );
+ });
+
+g.test('mismatched')
+ .desc(
+ `
+Validates that even with valid types, if arg0 and arg1 do not match types ${builtin}() errors
+`
+ )
+ .params(u => u.combine('arg0', keysOf(kValuesTypes)).combine('arg1', keysOf(kValuesTypes)))
+ .fn(t => {
+ const arg0 = kValuesTypes[t.params.arg0];
+ const arg1 = kValuesTypes[t.params.arg1];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ t.params.arg0 === t.params.arg1,
+ [arg0.create(0), arg1.create(1), u32(0), u32(32)],
+ 'constant'
+ );
+ });
+
+g.test('count_offset')
+ .desc(
+ `
+Validates that count and offset must be smaller than the size of the primitive.
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .beginSubcases()
+ .combineWithParams([
+ // offset + count < 32
+ { offset: 0, count: 31 },
+ { offset: 1, count: 30 },
+ { offset: 31, count: 0 },
+ { offset: 30, count: 1 },
+ // offset + count == 32
+ { offset: 0, count: 32 },
+ { offset: 1, count: 31 },
+ { offset: 16, count: 16 },
+ { offset: 31, count: 1 },
+ { offset: 32, count: 0 },
+ // offset + count > 32
+ { offset: 2, count: 31 },
+ { offset: 31, count: 2 },
+ // offset > 32
+ { offset: 33, count: 0 },
+ { offset: 33, count: 1 },
+ // count > 32
+ { offset: 0, count: 33 },
+ { offset: 1, count: 33 },
+ ] as const)
+ )
+ .fn(t => {
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ t.params.offset + t.params.count <= 32,
+ [u32(0), u32(1), u32(t.params.offset), u32(t.params.count)],
+ t.params.stage
+ );
+ });
+
+interface Argument {
+ /** Argument as a string. */
+ readonly arg: string;
+ /** Is this a valid argument type. Note that all args must be valid for the call to be valid. */
+ readonly pass: boolean;
+ /** Additional setup code necessary for this arg in the function scope. */
+ readonly preamble?: string;
+}
+
+function typesToArguments(types: readonly Type[], pass: boolean): Record<string, Argument> {
+ return types.reduce(
+ (res, type) => ({
+ ...res,
+ [type.toString()]: { arg: type.create(0).wgsl(), pass },
+ }),
+ {}
+ );
+}
+
+// u32 is included here to confirm that validation is failing due to a type issue and not something else.
+const kInputArgTypes: { readonly [name: string]: Argument } = {
+ ...typesToArguments([Type.u32], true),
+ ...typesToArguments([...kFloatScalarsAndVectors, Type.bool, Type.mat2x2f], false),
+ alias: { arg: 'u32_alias(1)', pass: true },
+ vec_bool: { arg: 'vec2<bool>(false,true)', pass: false },
+ atomic: { arg: 'a', pass: false },
+ array: {
+ preamble: 'var arry: array<u32, 5>;',
+ arg: 'arry',
+ pass: false,
+ },
+ array_runtime: { arg: 'k.arry', pass: false },
+ struct: {
+ preamble: 'var x: A;',
+ arg: 'x',
+ pass: false,
+ },
+ enumerant: { arg: 'read_write', pass: false },
+ ptr: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ arg: 'p',
+ pass: false,
+ },
+ ptr_deref: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ arg: '*p',
+ pass: true,
+ },
+ sampler: { arg: 's', pass: false },
+ texture: { arg: 't', pass: false },
+};
+
+g.test('typed_arguments')
+ .desc(
+ `
+Test compilation validation of ${builtin} with variously typed arguments
+ - The input types are matching to reduce testing permutations. Mismatching input types are
+ validated in 'mismatched' test above.
+ - For failing input types, just use the same type for offset and count to reduce combinations.
+`
+ )
+ .params(u =>
+ u
+ .combine('input', keysOf(kInputArgTypes))
+ .beginSubcases()
+ .combine('offset', keysOf(kInputArgTypes))
+ .expand('count', u => (kInputArgTypes[u.input].pass ? keysOf(kInputArgTypes) : [u.offset]))
+ )
+ .fn(t => {
+ const input = kInputArgTypes[t.params.input];
+ const offset = kInputArgTypes[t.params.offset];
+ const count = kInputArgTypes[t.params.count];
+ t.expectCompileResult(
+ input.pass && offset.pass && count.pass,
+ `alias u32_alias = u32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: u32,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${input.preamble ? input.preamble : ''}
+ ${offset.preamble && offset !== input ? offset.preamble : ''}
+ ${count.preamble && count !== input && count !== offset ? count.preamble : ''}
+ _ = ${builtin}(${input.arg},${input.arg},${offset.arg},${count.arg});
+ return vec4<f32>(.4, .2, .3, .1);
+ }`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(0u,1u,0u,1u); }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/inverseSqrt.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/inverseSqrt.spec.ts
index b2813cbe0a..806500d937 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/inverseSqrt.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/inverseSqrt.spec.ts
@@ -6,11 +6,9 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -18,7 +16,7 @@ import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinusTwoToTwo,
+ minusTwoToTwoRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -26,7 +24,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,17 +38,27 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(kMinusTwoToTwo, fullRangeForType(kValuesTypes[u.type])))
+ .expand('value', u =>
+ unique(
+ minusTwoToTwoRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
const expectedResult =
- t.params.value > 0 && isRepresentable(1 / Math.sqrt(t.params.value), elementType(type));
+ t.params.value > 0 &&
+ isRepresentable(
+ 1 / Math.sqrt(Number(t.params.value)),
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -60,22 +68,40 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
-g.test('integer_argument')
- .desc(
- `
-Validates that scalar and vector integer arguments are rejected by ${builtin}()
-`
- )
- .params(u => u.combine('type', keysOf(kIntegerArgumentTypes)))
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
.fn(t => {
- const type = kIntegerArgumentTypes[t.params.type];
- validateConstOrOverrideBuiltinEval(
- t,
- builtin,
- /* expectedResult */ type === TypeF32,
- [type.create(1)],
- 'constant'
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
);
});
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/length.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/length.spec.ts
index 60fbe6e285..003ad6811d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/length.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/length.spec.ts
@@ -7,14 +7,13 @@ import { makeTestGroup } from '../../../../../../common/framework/test_group.js'
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
ScalarType,
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalars,
- kAllFloatVector2,
- kAllFloatVector3,
- kAllFloatVector4,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalar,
+ kConvertableToFloatVec2,
+ kConvertableToFloatVec3,
+ kConvertableToFloatVec4,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -33,7 +32,7 @@ export const g = makeTestGroup(ShaderValidationTest);
* formed from `vec` of the element type `type`.
*/
function calculate(
- vec: number[],
+ vec: (number | bigint)[],
type: ScalarType
): {
/**
@@ -49,16 +48,25 @@ function calculate(
/** The computed value of length(). */
result: number;
} {
- const squareSum = vec.reduce((prev, curr) => prev + curr * curr, 0);
+ const vec_number = vec.map(e => Number(e));
+ const squareSum = vec_number.reduce((prev, curr) => prev + Number(curr) * Number(curr), 0);
const result = Math.sqrt(squareSum);
return {
- isIntermediateRepresentable: isRepresentable(squareSum, type),
- isResultRepresentable: isRepresentable(result, type),
+ isIntermediateRepresentable: isRepresentable(
+ squareSum,
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ ),
+ isResultRepresentable: isRepresentable(
+ result,
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ ),
result,
};
}
-const kScalarTypes = objectsToRecord(kAllFloatScalars);
+const kScalarTypes = objectsToRecord(kConvertableToFloatScalar);
g.test('scalar')
.desc(
@@ -76,7 +84,7 @@ the input scalar value always compiles without error
.expand('value', u => fullRangeForType(kScalarTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kScalarTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kScalarTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -92,7 +100,7 @@ the input scalar value always compiles without error
);
});
-const kVec2Types = objectsToRecord(kAllFloatVector2);
+const kVec2Types = objectsToRecord(kConvertableToFloatVec2);
g.test('vec2')
.desc(
@@ -108,11 +116,11 @@ Validates that constant evaluation and override evaluation of ${builtin}() with
.beginSubcases()
.expand('x', u => fullRangeForType(kVec2Types[u.type], 5))
.expand('y', u => fullRangeForType(kVec2Types[u.type], 5))
- .expand('_result', u => [calculate([u.x, u.y], elementType(kVec2Types[u.type]))])
+ .expand('_result', u => [calculate([u.x, u.y], scalarTypeOf(kVec2Types[u.type]))])
.filter(u => u._result.isResultRepresentable === u._result.isIntermediateRepresentable)
)
.beforeAllSubcases(t => {
- if (elementType(kVec2Types[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kVec2Types[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -127,7 +135,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() with
);
});
-const kVec3Types = objectsToRecord(kAllFloatVector3);
+const kVec3Types = objectsToRecord(kConvertableToFloatVec3);
g.test('vec3')
.desc(
@@ -144,11 +152,11 @@ Validates that constant evaluation and override evaluation of ${builtin}() with
.expand('x', u => fullRangeForType(kVec3Types[u.type], 4))
.expand('y', u => fullRangeForType(kVec3Types[u.type], 4))
.expand('z', u => fullRangeForType(kVec3Types[u.type], 4))
- .expand('_result', u => [calculate([u.x, u.y, u.z], elementType(kVec3Types[u.type]))])
+ .expand('_result', u => [calculate([u.x, u.y, u.z], scalarTypeOf(kVec3Types[u.type]))])
.filter(u => u._result.isResultRepresentable === u._result.isIntermediateRepresentable)
)
.beforeAllSubcases(t => {
- if (elementType(kVec3Types[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kVec3Types[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -163,7 +171,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() with
);
});
-const kVec4Types = objectsToRecord(kAllFloatVector4);
+const kVec4Types = objectsToRecord(kConvertableToFloatVec4);
g.test('vec4')
.desc(
@@ -181,11 +189,11 @@ Validates that constant evaluation and override evaluation of ${builtin}() with
.expand('y', u => fullRangeForType(kVec4Types[u.type], 3))
.expand('z', u => fullRangeForType(kVec4Types[u.type], 3))
.expand('w', u => fullRangeForType(kVec4Types[u.type], 3))
- .expand('_result', u => [calculate([u.x, u.y, u.z, u.w], elementType(kVec4Types[u.type]))])
+ .expand('_result', u => [calculate([u.x, u.y, u.z, u.w], scalarTypeOf(kVec4Types[u.type]))])
.filter(u => u._result.isResultRepresentable === u._result.isIntermediateRepresentable)
)
.beforeAllSubcases(t => {
- if (elementType(kVec4Types[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kVec4Types[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -200,7 +208,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() with
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -214,7 +222,7 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(1)],
'constant'
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log.spec.ts
index 5d84d0c0be..4ef0d553c9 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -23,7 +22,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,7 +39,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -55,7 +54,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -69,8 +68,41 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(1)],
'constant'
);
});
+
+const kArgCases = {
+ good: '(1.1)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2args: '(1.0,2.0)',
+ // Bad value type for arg 0
+ bad_0i32: '(1i)',
+ bad_0u32: '(1u)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log2.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log2.spec.ts
index 60f32d99c7..d242e1a410 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log2.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/log2.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -23,7 +22,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,7 +39,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -55,7 +54,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -69,8 +68,41 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(1)],
'constant'
);
});
+
+const kArgCases = {
+ good: '(1.1)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2args: '(1.0,2.0)',
+ // Bad value type for arg 0
+ bad_0i32: '(1i)',
+ bad_0u32: '(1u)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/max.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/max.spec.ts
new file mode 100644
index 0000000000..f32589fcf6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/max.spec.ts
@@ -0,0 +1,91 @@
+const builtin = 'max';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllNumericScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kAllNumericScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('a', u => fullRangeForType(kValuesTypes[u.type], 5))
+ .expand('b', u => fullRangeForType(kValuesTypes[u.type], 5))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kValuesTypes[t.params.type];
+ const expectedResult = true; // should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.a), type.create(t.params.b)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(1.1, 2.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_1arg: '(1.0)',
+ bad_3arg: '(1.0, 2.0, 3.0)',
+ // Bad value for arg 0
+ bad_0bool: '(false, 1.0)',
+ bad_0array: '(array(1.1,2.2), 1.0)',
+ bad_0struct: '(modf(2.2), 1.0)',
+ // Bad value type for arg 1
+ bad_1bool: '(1.0, true)',
+ bad_1array: '(1.0, array(1.1,2.2))',
+ bad_1struct: '(1.0, modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/min.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/min.spec.ts
new file mode 100644
index 0000000000..2222c44e92
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/min.spec.ts
@@ -0,0 +1,91 @@
+const builtin = 'min';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllNumericScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kAllNumericScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('a', u => fullRangeForType(kValuesTypes[u.type], 5))
+ .expand('b', u => fullRangeForType(kValuesTypes[u.type], 5))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kValuesTypes[t.params.type];
+ const expectedResult = true; // should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.a), type.create(t.params.b)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(1.1, 2.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_1arg: '(1.0)',
+ bad_3arg: '(1.0, 2.0, 3.0)',
+ // Bad value for arg 0
+ bad_0bool: '(false, 1.0)',
+ bad_0array: '(array(1.1,2.2), 1.0)',
+ bad_0struct: '(modf(2.2), 1.0)',
+ // Bad value type for arg 1
+ bad_1bool: '(1.0, true)',
+ bad_1array: '(1.0, array(1.1,2.2))',
+ bad_1struct: '(1.0, modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/modf.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/modf.spec.ts
index b890f9026e..2a90fa878e 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/modf.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/modf.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -23,7 +22,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,7 +39,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -55,7 +54,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -69,7 +68,7 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(0)],
'constant'
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/normalize.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/normalize.spec.ts
new file mode 100644
index 0000000000..28e1d9cdc6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/normalize.spec.ts
@@ -0,0 +1,146 @@
+const builtin = 'normalize';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatVectors,
+ scalarTypeOf,
+ ScalarType,
+} from '../../../../../util/conversion.js';
+import { QuantizeFunc, quantizeToF16, quantizeToF32 } from '../../../../../util/math.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord(kConvertableToFloatVectors);
+
+function quantizeFunctionForScalarType(type: ScalarType): QuantizeFunc<number> {
+ switch (type) {
+ case Type.f32:
+ return quantizeToF32;
+ case Type.f16:
+ return quantizeToF16;
+ default:
+ return (v: number) => v;
+ }
+}
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() rejects invalid values
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValidArgumentTypes[u.type]))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValidArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let expectedResult = true;
+
+ const scalarType = scalarTypeOf(kValidArgumentTypes[t.params.type]);
+ const quantizeFn = quantizeFunctionForScalarType(scalarType);
+
+ // Should be invalid if the normalization calculations result in intermediate
+ // values that exceed the maximum representable float value for the given type,
+ // or if the length is smaller than the smallest representable float value.
+ const v = Number(t.params.value);
+ const vv = quantizeFn(v * v);
+ const dp = quantizeFn(vv * kValidArgumentTypes[t.params.type].width);
+ const len = quantizeFn(Math.sqrt(dp));
+ if (vv === Infinity || dp === Infinity || len === 0) {
+ expectedResult = false;
+ }
+
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValidArgumentTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+const kInvalidArgumentTypes = objectsToRecord([
+ Type.f32,
+ Type.f16,
+ Type.abstractInt,
+ Type.bool,
+ Type.vec(2, Type.bool),
+ Type.vec(3, Type.bool),
+ Type.vec(4, Type.bool),
+ ...kConcreteIntegerScalarsAndVectors,
+]);
+
+g.test('invalid_argument')
+ .desc(
+ `
+Validates that all scalar arguments and vector integer or boolean arguments are rejected by ${builtin}()
+`
+ )
+ .params(u => u.combine('type', keysOf(kInvalidArgumentTypes)))
+ .beforeAllSubcases(t => {
+ if (kInvalidArgumentTypes[t.params.type] === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const expectedResult = false; // should always error with invalid argument types
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kInvalidArgumentTypes[t.params.type].create(0)],
+ 'constant'
+ );
+ });
+
+const kArgCases = {
+ good: '(vec3f(1, 0, 0))',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2args: '(vec3f(),vec3f())',
+ // Bad value for arg 0
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack2x16snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack2x16snorm.spec.ts
new file mode 100644
index 0000000000..f7b3718ca4
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack2x16snorm.spec.ts
@@ -0,0 +1,58 @@
+const kFn = 'pack2x16snorm';
+export const description = `Validate ${kFn}`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kArgCases = {
+ good: '(vec2f())',
+ good_vec2_abstract_float: '(vec2(0.1))',
+ bad_0args: '()',
+ bad_2args: '(vec2f(),vec2f())',
+ bad_abstract_int: '(1)',
+ bad_i32: '(1i)',
+ bad_f32: '(1f)',
+ bad_u32: '(1u)',
+ bad_abstract_float: '(0.1)',
+ bad_bool: '(false)',
+ bad_vec4f: '(vec4f())',
+ bad_vec4u: '(vec4u())',
+ bad_vec4i: '(vec4i())',
+ bad_vec4b: '(vec4<bool>())',
+ bad_vec3f: '(vec3f())',
+ bad_array: '(array(1.0, 2.0, 3.0, 4.0))',
+ bad_struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+const kReturnType = 'u32';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good' || t.params.arg === 'good_vec2_abstract_float',
+ `const c = ${kFn}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('return')
+ .desc(`Test ${kFn} return value type`)
+ .params(u => u.combine('type', ['u32', 'i32', 'f32', 'bool', 'vec2u']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.type === kReturnType,
+ `const c: ${t.params.type} = ${kFn}${kGoodArgs};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack2x16unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack2x16unorm.spec.ts
new file mode 100644
index 0000000000..4b6c22f02f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack2x16unorm.spec.ts
@@ -0,0 +1,58 @@
+const kFn = 'pack2x16unorm';
+export const description = `Validate ${kFn}`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kArgCases = {
+ good: '(vec2f())',
+ good_vec2_abstract_float: '(vec2(0.1))',
+ bad_0args: '()',
+ bad_2args: '(vec2f(),vec2f())',
+ bad_abstract_int: '(1)',
+ bad_i32: '(1i)',
+ bad_f32: '(1f)',
+ bad_u32: '(1u)',
+ bad_abstract_float: '(0.1)',
+ bad_bool: '(false)',
+ bad_vec4f: '(vec4f())',
+ bad_vec4u: '(vec4u())',
+ bad_vec4i: '(vec4i())',
+ bad_vec4b: '(vec4<bool>())',
+ bad_vec3f: '(vec3f())',
+ bad_array: '(array(1.0, 2.0, 3.0, 4.0))',
+ bad_struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+const kReturnType = 'u32';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good' || t.params.arg === 'good_vec2_abstract_float',
+ `const c = ${kFn}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('return')
+ .desc(`Test ${kFn} return value type`)
+ .params(u => u.combine('type', ['u32', 'i32', 'f32', 'bool', 'vec2u']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.type === kReturnType,
+ `const c: ${t.params.type} = ${kFn}${kGoodArgs};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4x8snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4x8snorm.spec.ts
new file mode 100644
index 0000000000..ea7e196769
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4x8snorm.spec.ts
@@ -0,0 +1,58 @@
+const kFn = 'pack4x8snorm';
+export const description = `Validate ${kFn}`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kArgCases = {
+ good: '(vec4f())',
+ good_vec4_abstract_float: '(vec4(0.1))',
+ bad_0args: '()',
+ bad_2args: '(vec4f(),vec4f())',
+ bad_abstract_int: '(1)',
+ bad_i32: '(1i)',
+ bad_f32: '(1f)',
+ bad_u32: '(1u)',
+ bad_abstract_float: '(0.1)',
+ bad_bool: '(false)',
+ bad_vec4u: '(vec4u())',
+ bad_vec4i: '(vec4i())',
+ bad_vec4b: '(vec4<bool>())',
+ bad_vec2f: '(vec2f())',
+ bad_vec3f: '(vec3f())',
+ bad_array: '(array(1.0, 2.0, 3.0, 4.0))',
+ bad_struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+const kReturnType = 'u32';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good' || t.params.arg === 'good_vec4_abstract_float',
+ `const c = ${kFn}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('return')
+ .desc(`Test ${kFn} return value type`)
+ .params(u => u.combine('type', ['u32', 'i32', 'f32', 'bool', 'vec2u']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.type === kReturnType,
+ `const c: ${t.params.type} = ${kFn}${kGoodArgs};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4x8unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4x8unorm.spec.ts
new file mode 100644
index 0000000000..46aafcec88
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4x8unorm.spec.ts
@@ -0,0 +1,58 @@
+const kFn = 'pack4x8unorm';
+export const description = `Validate ${kFn}`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kArgCases = {
+ good: '(vec4f())',
+ good_vec4_abstract_float: '(vec4(0.1))',
+ bad_0args: '()',
+ bad_2args: '(vec4f(),vec4f())',
+ bad_abstract_int: '(1)',
+ bad_i32: '(1i)',
+ bad_f32: '(1f)',
+ bad_u32: '(1u)',
+ bad_abstract_float: '(0.1)',
+ bad_bool: '(false)',
+ bad_vec4u: '(vec4u())',
+ bad_vec4i: '(vec4i())',
+ bad_vec4b: '(vec4<bool>())',
+ bad_vec2f: '(vec2f())',
+ bad_vec3f: '(vec3f())',
+ bad_array: '(array(1.0, 2.0, 3.0, 4.0))',
+ bad_struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+const kReturnType = 'u32';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good' || t.params.arg === 'good_vec4_abstract_float',
+ `const c = ${kFn}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('return')
+ .desc(`Test ${kFn} return value type`)
+ .params(u => u.combine('type', ['u32', 'i32', 'f32', 'bool', 'vec2u']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.type === kReturnType,
+ `const c: ${t.params.type} = ${kFn}${kGoodArgs};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xI8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xI8.spec.ts
new file mode 100644
index 0000000000..21b7b706cf
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xI8.spec.ts
@@ -0,0 +1,62 @@
+export const description = `Validate pack4xI8`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kFeature = 'packed_4x8_integer_dot_product';
+const kFn = 'pack4xI8';
+const kArgCases = {
+ good: '(vec4i())',
+ bad_0args: '()',
+ bad_2args: '(vec4i(),vec4i())',
+ bad_0i32: '(1i)',
+ bad_0f32: '(1f)',
+ bad_0bool: '(false)',
+ bad_0vec4u: '(vec4u())',
+ bad_0vec4f: '(vec4f())',
+ bad_0vec4b: '(vec4<bool>())',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec3i: '(vec3i())',
+ bad_0array: '(array(1))',
+ bad_0struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('unsupported')
+ .desc(`Test absence of ${kFn} when ${kFeature} is not supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(false, code);
+ });
+
+g.test('supported')
+ .desc(`Test presence of ${kFn} when ${kFeature} is supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(true, code);
+ });
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ t.expectCompileResult(t.params.arg === 'good', `const c = ${kFn}${kArgCases[t.params.arg]};`);
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xI8Clamp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xI8Clamp.spec.ts
new file mode 100644
index 0000000000..7af8958bb1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xI8Clamp.spec.ts
@@ -0,0 +1,62 @@
+export const description = `Validate pack4xI8Clamp`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kFeature = 'packed_4x8_integer_dot_product';
+const kFn = 'pack4xI8Clamp';
+const kArgCases = {
+ good: '(vec4i())',
+ bad_0args: '()',
+ bad_2args: '(vec4i(),vec4i())',
+ bad_0i32: '(1i)',
+ bad_0f32: '(1f)',
+ bad_0bool: '(false)',
+ bad_0vec4u: '(vec4u())',
+ bad_0vec4f: '(vec4f())',
+ bad_0vec4b: '(vec4<bool>())',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec3i: '(vec3i())',
+ bad_0array: '(array(1))',
+ bad_0struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('unsupported')
+ .desc(`Test absence of ${kFn} when ${kFeature} is not supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(false, code);
+ });
+
+g.test('supported')
+ .desc(`Test presence of ${kFn} when ${kFeature} is supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(true, code);
+ });
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ t.expectCompileResult(t.params.arg === 'good', `const c = ${kFn}${kArgCases[t.params.arg]};`);
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xU8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xU8.spec.ts
new file mode 100644
index 0000000000..89daf34f84
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xU8.spec.ts
@@ -0,0 +1,62 @@
+export const description = `Validate pack4xU8`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kFeature = 'packed_4x8_integer_dot_product';
+const kFn = 'pack4xU8';
+const kArgCases = {
+ good: '(vec4u())',
+ bad_0args: '()',
+ bad_2args: '(vec4u(),vec4u())',
+ bad_0i32: '(1i)',
+ bad_0f32: '(1f)',
+ bad_0bool: '(false)',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4f: '(vec4f())',
+ bad_0vec4b: '(vec4<bool>())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3u: '(vec3u())',
+ bad_0array: '(array(1))',
+ bad_0struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('unsupported')
+ .desc(`Test absence of ${kFn} when ${kFeature} is not supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(false, code);
+ });
+
+g.test('supported')
+ .desc(`Test presence of ${kFn} when ${kFeature} is supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(true, code);
+ });
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ t.expectCompileResult(t.params.arg === 'good', `const c = ${kFn}${kArgCases[t.params.arg]};`);
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xU8Clamp.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xU8Clamp.spec.ts
new file mode 100644
index 0000000000..9d7bd0353b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/pack4xU8Clamp.spec.ts
@@ -0,0 +1,62 @@
+export const description = `Validate pack4xU8Clamp`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kFeature = 'packed_4x8_integer_dot_product';
+const kFn = 'pack4xU8Clamp';
+const kArgCases = {
+ good: '(vec4u())',
+ bad_0args: '()',
+ bad_2args: '(vec4u(),vec4u())',
+ bad_0i32: '(1i)',
+ bad_0f32: '(1f)',
+ bad_0bool: '(false)',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4f: '(vec4f())',
+ bad_0vec4b: '(vec4<bool>())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3u: '(vec3u())',
+ bad_0array: '(array(1))',
+ bad_0struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('unsupported')
+ .desc(`Test absence of ${kFn} when ${kFeature} is not supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(false, code);
+ });
+
+g.test('supported')
+ .desc(`Test presence of ${kFn} when ${kFeature} is supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(true, code);
+ });
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ t.expectCompileResult(t.params.arg === 'good', `const c = ${kFn}${kArgCases[t.params.arg]};`);
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/quantizeToF16.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/quantizeToF16.spec.ts
new file mode 100644
index 0000000000..4cad84e78c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/quantizeToF16.spec.ts
@@ -0,0 +1,113 @@
+const builtin = 'quantizeToF16';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import { Type, kConcreteF32ScalarsAndVectors } from '../../../../../util/conversion.js';
+import { quantizeToF16 } from '../../../../../util/math.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord([
+ Type.abstractFloat,
+ Type.vec(2, Type.abstractFloat),
+ Type.vec(3, Type.abstractFloat),
+ Type.vec(4, Type.abstractFloat),
+ ...kConcreteF32ScalarsAndVectors,
+]);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() error on invalid inputs.
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValidArgumentTypes[u.type]))
+ )
+ .fn(t => {
+ let expectedResult = true;
+
+ // Should be invalid if the quantized value exceeds the maximum representable
+ // 16-bit float value.
+ const f16Value = quantizeToF16(Number(t.params.value));
+ if (f16Value === Infinity || f16Value === -Infinity) {
+ expectedResult = false;
+ }
+
+ const type = kValidArgumentTypes[t.params.type];
+
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+const kArgCasesF16 = {
+ bad_0f16: '(1h)',
+ bad_0vec2h: '(vec2h())',
+ bad_0vec3h: '(vec3h())',
+ bad_0vec4h: '(vec4h())',
+};
+
+const kArgCases = {
+ good: '(vec3f())',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.0, 2.0)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+ ...kArgCasesF16,
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.arg in kArgCasesF16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/radians.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/radians.spec.ts
index dd432ac194..9017231b69 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/radians.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/radians.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -23,7 +22,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,7 +39,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -55,7 +54,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -69,8 +68,41 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(1)],
'constant'
);
});
+
+const kArgCases = {
+ good: '(1.1)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_too_few: '()',
+ bad_too_many: '(1.0,2.0)',
+ // Bad value type for arg 0
+ bad_0i32: '(1i)',
+ bad_0u32: '(1u)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/reflect.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/reflect.spec.ts
new file mode 100644
index 0000000000..c71f37f895
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/reflect.spec.ts
@@ -0,0 +1,131 @@
+const builtin = 'reflect';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConvertableToFloatVectors,
+ scalarTypeOf,
+ ScalarType,
+} from '../../../../../util/conversion.js';
+import { QuantizeFunc, quantizeToF16, quantizeToF32 } from '../../../../../util/math.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord(kConvertableToFloatVectors);
+
+function quantizeFunctionForScalarType(type: ScalarType): QuantizeFunc<number> {
+ switch (type) {
+ case Type.f32:
+ return quantizeToF32;
+ case Type.f16:
+ return quantizeToF16;
+ default:
+ return (v: number) => v;
+ }
+}
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('a', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ .expand('b', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValidArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let expectedResult = true;
+
+ const scalarType = scalarTypeOf(kValidArgumentTypes[t.params.type]);
+ const quantizeFn = quantizeFunctionForScalarType(scalarType);
+
+ // Reflect equation: a - 2 * dot(b, a) * b
+ // Should be invalid if the reflect calculations result in intermediate
+ // values that exceed the maximum representable float value for the given type.
+ const a = Number(t.params.a);
+ const b = Number(t.params.b);
+ const ab = quantizeFn(a * b);
+ const dp = quantizeFn(ab * kValidArgumentTypes[t.params.type].width);
+ const dp2 = quantizeFn(dp * 2);
+ const dp2b = quantizeFn(dp2 * b);
+ const a_dp2b = quantizeFn(a - dp2b);
+
+ if (
+ !Number.isFinite(ab) ||
+ !Number.isFinite(dp) ||
+ !Number.isFinite(dp2) ||
+ !Number.isFinite(dp2b) ||
+ !Number.isFinite(a_dp2b)
+ ) {
+ expectedResult = false;
+ }
+
+ const type = kValidArgumentTypes[t.params.type];
+
+ // Validates reflect(vecN(a, a, a), vecN(b, b, b));
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.a), type.create(t.params.b)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(vec3(0), vec3(1))',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_1arg: '(vec3(0))',
+ bad_3arg: '(vec3(0), vec3(1), vec3(2))',
+ // Bad value for arg 0
+ bad_0bool: '(false, vec3(1))',
+ bad_0array: '(array(1.1,2.2), vec3(1))',
+ bad_0struct: '(modf(2.2), vec3(1))',
+ // Bad value type for arg 1
+ bad_1bool: '(vec3(0), true)',
+ bad_1array: '(vec3(0), array(1.1,2.2))',
+ bad_1struct: '(vec3(0), modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/reverseBits.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/reverseBits.spec.ts
new file mode 100644
index 0000000000..8b901bb595
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/reverseBits.spec.ts
@@ -0,0 +1,198 @@
+const builtin = 'reverseBits';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kFloatScalarsAndVectors,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConcreteIntegerScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() never errors
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type]))
+ )
+ .fn(t => {
+ const expectedResult = true; // reverseBits() should never error
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [kValuesTypes[t.params.type].create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+// u32 is included here to confirm that validation is failing due to a type issue and not something else.
+const kFloatTypes = objectsToRecord([Type.u32, ...kFloatScalarsAndVectors]);
+
+g.test('float_argument')
+ .desc(
+ `
+Validates that float arguments are rejected by ${builtin}()
+`
+ )
+ .params(u => u.combine('type', keysOf(kFloatTypes)))
+ .fn(t => {
+ const type = kFloatTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ type === Type.u32,
+ [type.create(0)],
+ 'constant'
+ );
+ });
+
+const kTests: {
+ readonly [name: string]: {
+ /** Arguments to pass to the builtin with parentheses. */
+ readonly args: string;
+ /** Should the test case pass. */
+ readonly pass: boolean;
+ /** Additional setup code in the function scope. */
+ readonly preamble?: string;
+ };
+} = {
+ valid: {
+ args: '(1u)',
+ pass: true,
+ },
+ // Number of arguments.
+ no_parens: {
+ args: '',
+ pass: false,
+ },
+ too_few_args: {
+ args: '()',
+ pass: false,
+ },
+ too_many_args: {
+ args: '(1u,2u)',
+ pass: false,
+ },
+ // Arguments types (only 1 argument for this builtin).
+ alias: {
+ args: '(u32_alias(1))',
+ pass: true,
+ },
+ bool: {
+ args: '(false)',
+ pass: false,
+ },
+ vec_bool: {
+ args: '(vec2<bool>(false,true))',
+ pass: false,
+ },
+ matrix: {
+ args: '(mat2x2(1,1,1,1))',
+ pass: false,
+ },
+ atomic: {
+ args: '(a)',
+ pass: false,
+ },
+ array: {
+ preamble: 'var arry: array<u32, 5>;',
+ args: '(arry)',
+ pass: false,
+ },
+ array_runtime: {
+ args: '(k.arry)',
+ pass: false,
+ },
+ struct: {
+ preamble: 'var x: A;',
+ args: '(x)',
+ pass: false,
+ },
+ enumerant: {
+ args: '(read_write)',
+ pass: false,
+ },
+ ptr: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(p)',
+ pass: false,
+ },
+ ptr_deref: {
+ preamble: `var<function> f = 1u;
+ let p: ptr<function, u32> = &f;`,
+ args: '(*p)',
+ pass: true,
+ },
+ sampler: {
+ args: '(s)',
+ pass: false,
+ },
+ texture: {
+ args: '(t)',
+ pass: false,
+ },
+};
+
+g.test('arguments')
+ .desc(`Test compilation validation of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .fn(t => {
+ const test = kTests[t.params.test];
+ t.expectCompileResult(
+ test.pass,
+ `alias u32_alias = u32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: u32,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${test.preamble ? test.preamble : ''}
+ _ = ${builtin}${test.args};
+ return vec4<f32>(.4, .2, .3, .1);
+ }`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(1u); }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/round.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/round.spec.ts
index 3a4ea0408a..dae7482c18 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/round.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/round.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { fpTraitsFor } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -25,7 +24,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,15 +39,19 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
.expand('value', u => {
- const constants = fpTraitsFor(elementType(kValuesTypes[u.type])).constants();
- return unique(fullRangeForType(kValuesTypes[u.type]), [
- constants.negative.min + 0.1,
- constants.positive.max - 0.1,
- ]);
+ if (scalarTypeOf(kValuesTypes[u.type]).kind === 'abstract-int') {
+ return fullRangeForType(kValuesTypes[u.type]);
+ } else {
+ const constants = fpTraitsFor(scalarTypeOf(kValuesTypes[u.type])).constants();
+ return unique(fullRangeForType(kValuesTypes[u.type]), [
+ constants.negative.min + 0.1,
+ constants.positive.max - 0.1,
+ ]);
+ }
})
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -63,7 +66,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -77,7 +80,7 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(1)],
'constant'
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/saturate.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/saturate.spec.ts
index 1c7aa66a65..cbd1b3f369 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/saturate.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/saturate.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -23,7 +22,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,7 +39,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -55,7 +54,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -69,7 +68,7 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(1)],
'constant'
);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/select.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/select.spec.ts
new file mode 100644
index 0000000000..b03ec66b7d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/select.spec.ts
@@ -0,0 +1,250 @@
+const builtin = 'select';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ concreteTypeOf,
+ isConvertible,
+ kAllScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import { validateConstOrOverrideBuiltinEval } from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kArgumentTypes = objectsToRecord(kAllScalarsAndVectors);
+
+g.test('argument_types_1_and_2')
+ .desc(
+ `
+Validates that scalar and vector arguments are not rejected by ${builtin}() for args 1 and 2
+`
+ )
+ .params(u => u.combine('type1', keysOf(kArgumentTypes)).combine('type2', keysOf(kArgumentTypes)))
+ .beforeAllSubcases(t => {
+ if (
+ scalarTypeOf(kArgumentTypes[t.params.type1]) === Type.f16 ||
+ scalarTypeOf(kArgumentTypes[t.params.type2]) === Type.f16
+ ) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type1 = kArgumentTypes[t.params.type1];
+ const type2 = kArgumentTypes[t.params.type2];
+ // First and second arg must be the same or one convertible to the other.
+ // Note that we specify a concrete return type even if both args are abstract.
+ const returnType = isConvertible(type1, type2)
+ ? concreteTypeOf(type2)
+ : isConvertible(type2, type1)
+ ? concreteTypeOf(type1)
+ : undefined;
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ returnType !== undefined,
+ [type1.create(0), type2.create(0), Type.bool.create(0)],
+ 'constant',
+ returnType
+ );
+ });
+
+g.test('argument_types_3')
+ .desc(
+ `
+Validates that third argument must be bool for ${builtin}()
+`
+ )
+ .params(u => u.combine('type', keysOf(kArgumentTypes)))
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ type === Type.bool,
+ [Type.i32.create(0), Type.i32.create(0), type.create(0)],
+ 'constant',
+ /*return_type*/ Type.i32
+ );
+ });
+
+const kTests = {
+ valid: {
+ src: `_ = ${builtin}(1, 2, true);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = ${builtin}(i32_alias(1), i32_alias(2), bool_alias(true));`,
+ pass: true,
+ },
+ bool: {
+ src: `_ = ${builtin}(false, false, true);`,
+ pass: true,
+ },
+ i32: {
+ src: `_ = ${builtin}(1i, 1i, true);`,
+ pass: true,
+ },
+ u32: {
+ src: `_ = ${builtin}(1u, 1u, true);`,
+ pass: true,
+ },
+ f32: {
+ src: `_ = ${builtin}(1.0f, 1.0f, true);`,
+ pass: true,
+ },
+ f16: {
+ src: `_ = ${builtin}(1.0h, 1.0h, true);`,
+ pass: true,
+ },
+ mixed_aint_afloat: {
+ src: `_ = ${builtin}(1, 1.0, true);`,
+ pass: true,
+ },
+ mixed_i32_u32: {
+ src: `_ = ${builtin}(1i, 1u, true);`,
+ pass: false,
+ },
+ vec_bool: {
+ src: `_ = ${builtin}(vec2<bool>(false, true), vec2<bool>(false, true), true);`,
+ pass: true,
+ },
+ vec2_bool_implicit: {
+ src: `_ = ${builtin}(vec2(false, true), vec2(false, true), true);`,
+ pass: true,
+ },
+ vec3_bool_implicit: {
+ src: `_ = ${builtin}(vec3(false), vec3(true), true);`,
+ pass: true,
+ },
+ vec_i32: {
+ src: `_ = ${builtin}(vec2<i32>(1, 1), vec2<i32>(1, 1), true);`,
+ pass: true,
+ },
+ vec_u32: {
+ src: `_ = ${builtin}(vec2<u32>(1, 1), vec2<u32>(1, 1), true);`,
+ pass: true,
+ },
+ vec_f32: {
+ src: `_ = ${builtin}(vec2<f32>(1, 1), vec2<f32>(1, 1), true);`,
+ pass: true,
+ },
+ vec_f16: {
+ src: `_ = ${builtin}(vec2<f16>(1, 1), vec2<f16>(1, 1), true);`,
+ pass: true,
+ },
+ matrix: {
+ src: `_ = ${builtin}(mat2x2(1, 1, 1, 1), mat2x2(1, 1, 1, 1), true);`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = ${builtin}(a, a, true);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<bool, 5>;
+ _ = ${builtin}(a, a, true);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = ${builtin}(k.arry, k.arry, true);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = ${builtin}(a, a, true);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = ${builtin}(read_write, read_write, true);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = true;
+ let p: ptr<function, bool> = &a;
+ _ = ${builtin}(p, p, true);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = true;
+ let p: ptr<function, bool> = &a;
+ _ = ${builtin}(*p, *p, true);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = ${builtin}(s, s, true);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = ${builtin}(t, t, true);`,
+ pass: false,
+ },
+ no_args: {
+ src: `_ = ${builtin}();`,
+ pass: false,
+ },
+ too_few_args: {
+ src: `_ = ${builtin}(1, true);`,
+ pass: false,
+ },
+ too_many_args: {
+ src: `_ = ${builtin}(1, 1, 1, true);`,
+ pass: false,
+ },
+};
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}(1, 2, true); }`);
+ });
+
+g.test('arguments')
+ .desc(`Test that ${builtin} is validated correctly.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .beforeAllSubcases(t => {
+ if (t.params.test.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const enables = t.params.test.includes('f16') ? 'enable f16;' : '';
+ const code = `
+ ${enables}
+ alias bool_alias = bool;
+ alias i32_alias = i32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: bool,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+ }`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/shader_stage_utils.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/shader_stage_utils.ts
new file mode 100644
index 0000000000..34e5bc530d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/shader_stage_utils.ts
@@ -0,0 +1,64 @@
+/**
+ * Use to test that certain WGSL builtins are only available in the fragment stage.
+ * Create WGSL that defines a function "foo" and its required variables that uses
+ * the builtin being tested. Append it to these code strings then compile. It should
+ * succeed or fail based on the value `expectSuccess`.
+ *
+ * See ./textureSample.spec.ts was one example
+ */
+export const kEntryPointsToValidateFragmentOnlyBuiltins = {
+ none: {
+ expectSuccess: true,
+ code: ``,
+ },
+ fragment: {
+ expectSuccess: true,
+ code: `
+ @fragment
+ fn main() {
+ foo();
+ }
+ `,
+ },
+ vertex: {
+ expectSuccess: false,
+ code: `
+ @vertex
+ fn main() -> @builtin(position) vec4f {
+ foo();
+ return vec4f();
+ }
+ `,
+ },
+ compute: {
+ expectSuccess: false,
+ code: `
+ @compute @workgroup_size(1)
+ fn main() {
+ foo();
+ }
+ `,
+ },
+ fragment_and_compute: {
+ expectSuccess: false,
+ code: `
+ @fragment
+ fn main1() {
+ foo();
+ }
+
+ @compute @workgroup_size(1)
+ fn main2() {
+ foo();
+ }
+ `,
+ },
+ compute_without_call: {
+ expectSuccess: true,
+ code: `
+ @compute @workgroup_size(1)
+ fn main() {
+ }
+ `,
+ },
+};
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sign.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sign.spec.ts
index f844961aee..2f1e33b101 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sign.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sign.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatAndSignedIntegerScalarsAndVectors,
- kAllUnsignedIntegerScalarsAndVectors,
+ Type,
+ kFloatScalarsAndVectors,
+ kConcreteSignedIntegerScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -23,7 +22,10 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatAndSignedIntegerScalarsAndVectors);
+const kValuesTypes = objectsToRecord([
+ ...kFloatScalarsAndVectors,
+ ...kConcreteSignedIntegerScalarsAndVectors,
+]);
g.test('values')
.desc(
@@ -40,7 +42,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -55,25 +57,36 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
);
});
-const kUnsignedIntegerArgumentTypes = objectsToRecord([
- TypeF32,
- ...kAllUnsignedIntegerScalarsAndVectors,
-]);
+const kArgCases = {
+ good: '(1.0)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.0, 1.0)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0vec2u: '(vec2u(1))',
+ bad_0vec3u: '(vec3u(1))',
+ bad_0vec4u: '(vec4u(1))',
+};
-g.test('unsigned_integer_argument')
- .desc(
- `
-Validates that scalar and vector integer arguments are rejected by ${builtin}()
-`
- )
- .params(u => u.combine('type', keysOf(kUnsignedIntegerArgumentTypes)))
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
.fn(t => {
- const type = kUnsignedIntegerArgumentTypes[t.params.type];
- validateConstOrOverrideBuiltinEval(
- t,
- builtin,
- /* expectedResult */ type === TypeF32,
- [type.create(1)],
- 'constant'
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
);
});
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sin.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sin.spec.ts
index 3822fccd3a..6be01123cc 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sin.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sin.spec.ts
@@ -6,18 +6,17 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinus3PiTo3Pi,
+ minusThreePiToThreePiRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -25,7 +24,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -39,10 +38,15 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(kMinus3PiTo3Pi, fullRangeForType(kValuesTypes[u.type])))
+ .expand('value', u =>
+ unique(
+ minusThreePiToThreePiRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
@@ -56,7 +60,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -70,8 +74,42 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
+
[type.create(0)],
'constant'
);
});
+
+const kArgCases = {
+ good: '(1.1)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2args: '(1.0,2.0)',
+ // Bad value type for arg 0
+ bad_0i32: '(1i)',
+ bad_0u32: '(1u)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sinh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sinh.spec.ts
index 09f48751fc..fc43d23cdd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sinh.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sinh.spec.ts
@@ -6,11 +6,9 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -24,7 +22,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -41,13 +39,17 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.expand('value', u => fullRangeForType(kValuesTypes[u.type]))
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
- const expectedResult = isRepresentable(Math.sinh(t.params.value), elementType(type));
+ const expectedResult = isRepresentable(
+ Math.sinh(Number(t.params.value)),
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -57,22 +59,40 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
-g.test('integer_argument')
- .desc(
- `
-Validates that scalar and vector integer arguments are rejected by ${builtin}()
-`
- )
- .params(u => u.combine('type', keysOf(kIntegerArgumentTypes)))
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
.fn(t => {
- const type = kIntegerArgumentTypes[t.params.type];
- validateConstOrOverrideBuiltinEval(
- t,
- builtin,
- /* expectedResult */ type === TypeF32,
- [type.create(0)],
- 'constant'
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
);
});
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/smoothstep.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/smoothstep.spec.ts
new file mode 100644
index 0000000000..643e5df09e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/smoothstep.spec.ts
@@ -0,0 +1,241 @@
+const builtin = 'smoothstep';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ concreteTypeOf,
+ elementTypeOf,
+ isConvertibleToFloatType,
+ kAllScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+const kArgumentTypes = objectsToRecord(kAllScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() rejects invalid values
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value1', u => [-1000, -10, 0, 10, 1000])
+ .expand('value2', u => [-1000, -10, 0, 10, 1000])
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kValuesTypes[t.params.type];
+
+ // We expect to fail if low == high as it results in a DBZ
+ const expectedResult = t.params.value1 !== t.params.value2;
+
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value1), type.create(t.params.value2), type.create(0)],
+ t.params.stage,
+ /* returnType */ concreteTypeOf(type, [Type.f32])
+ );
+ });
+
+g.test('argument_types')
+ .desc(
+ `
+Validates that scalar and vector arguments are rejected by ${builtin}() if not float type or vecN<float type>
+`
+ )
+ .params(u => u.combine('type', keysOf(kArgumentTypes)))
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ /* expectedResult */ isConvertibleToFloatType(elementTypeOf(type)),
+ [type.create(0), type.create(1), type.create(2)],
+ 'constant',
+ /* returnType */ concreteTypeOf(type, [Type.f32])
+ );
+ });
+
+const kTests = {
+ valid: {
+ src: `_ = ${builtin}(0.0, 42.0, 0.5);`,
+ pass: true,
+ },
+ alias: {
+ src: `_ = ${builtin}(f32_alias(0), f32_alias(42), f32_alias(0.5));`,
+ pass: true,
+ },
+ bool: {
+ src: `_ = ${builtin}(false, false, false);`,
+ pass: false,
+ },
+ i32: {
+ src: `_ = ${builtin}(1i, 2i, 1i);`,
+ pass: false,
+ },
+ u32: {
+ src: `_ = ${builtin}(1u, 2u, 1u);`,
+ pass: false,
+ },
+ f32: {
+ src: `_ = ${builtin}(1.0f, 2.0f, 1.0f);`,
+ pass: true,
+ },
+ f16: {
+ src: `_ = ${builtin}(1h, 2h, 1h);`,
+ pass: true,
+ },
+ mixed_aint_afloat: {
+ src: `_ = ${builtin}(1.0, 2, 1);`,
+ pass: true,
+ },
+ mixed_f32_afloat: {
+ src: `_ = ${builtin}(1.0f, 2.0, 1.0);`,
+ pass: true,
+ },
+ mixed_f16_afloat: {
+ src: `_ = ${builtin}(1.0h, 2.0, 1.0);`,
+ pass: true,
+ },
+ vec_bool: {
+ src: `_ = ${builtin}(vec2<bool>(false, true), vec2<bool>(false, true), vec2<bool>(false, true));`,
+ pass: false,
+ },
+ vec_i32: {
+ src: `_ = ${builtin}(vec2<i32>(1, 1), vec2<i32>(1, 1), vec2<i32>(1, 1));`,
+ pass: false,
+ },
+ vec_u32: {
+ src: `_ = ${builtin}(vec2<u32>(1, 1), vec2<u32>(1, 1), vec2<u32>(1, 1));`,
+ pass: false,
+ },
+ vec_f32: {
+ src: `_ = ${builtin}(vec2<f32>(0, 0), vec2<f32>(1, 1), vec2<f32>(1, 1));`,
+ pass: true,
+ },
+ matrix: {
+ src: `_ = ${builtin}(mat2x2(1, 1, 1, 1), mat2x2(1, 1, 1, 1), mat2x2(1, 1, 1, 1));`,
+ pass: false,
+ },
+ atomic: {
+ src: ` _ = ${builtin}(a, a, a);`,
+ pass: false,
+ },
+ array: {
+ src: `var a: array<bool, 5>;
+ _ = ${builtin}(a, a, a);`,
+ pass: false,
+ },
+ array_runtime: {
+ src: `_ = ${builtin}(k.arry, k.arry, k.arry);`,
+ pass: false,
+ },
+ struct: {
+ src: `var a: A;
+ _ = ${builtin}(a, a, a);`,
+ pass: false,
+ },
+ enumerant: {
+ src: `_ = ${builtin}(read_write, read_write, read_write);`,
+ pass: false,
+ },
+ ptr: {
+ src: `var<function> a = 1.0;
+ let p: ptr<function, f32> = &a;
+ _ = ${builtin}(p, p, p);`,
+ pass: false,
+ },
+ ptr_deref: {
+ src: `var<function> a = 1.0;
+ let p: ptr<function, f32> = &a;
+ _ = ${builtin}(*p, *p, *p);`,
+ pass: true,
+ },
+ sampler: {
+ src: `_ = ${builtin}(s, s, s);`,
+ pass: false,
+ },
+ texture: {
+ src: `_ = ${builtin}(t, t, t);`,
+ pass: false,
+ },
+ no_args: {
+ src: `_ = ${builtin}();`,
+ pass: false,
+ },
+ too_few_args: {
+ src: `_ = ${builtin}(1.0, 2.0);`,
+ pass: false,
+ },
+ too_many_args: {
+ src: `_ = ${builtin}(1.0, 2.0, 3.0, 4.0);`,
+ pass: false,
+ },
+};
+
+g.test('arguments')
+ .desc(`Test that ${builtin} is validated correctly when called with different arguments.`)
+ .params(u => u.combine('test', keysOf(kTests)))
+ .beforeAllSubcases(t => {
+ if (t.params.test.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const src = kTests[t.params.test].src;
+ const enables = t.params.test.includes('f16') ? 'enable f16;' : '';
+ const code = `
+ ${enables}
+ alias f32_alias = f32;
+
+ @group(0) @binding(0) var s: sampler;
+ @group(0) @binding(1) var t: texture_2d<f32>;
+
+ var<workgroup> a: atomic<u32>;
+
+ struct A {
+ i: bool,
+ }
+ struct B {
+ arry: array<u32>,
+ }
+ @group(0) @binding(3) var<storage> k: B;
+
+ @vertex
+ fn main() -> @builtin(position) vec4<f32> {
+ ${src}
+ return vec4<f32>(.4, .2, .3, .1);
+ }`;
+ t.expectCompileResult(kTests[t.params.test].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sqrt.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sqrt.spec.ts
index a570ce4bc0..cabb0d59fb 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sqrt.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/sqrt.spec.ts
@@ -6,11 +6,10 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConcreteIntegerScalarsAndVectors,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { isRepresentable } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -18,7 +17,7 @@ import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinusTwoToTwo,
+ minusTwoToTwoRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -26,7 +25,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,17 +39,27 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(kMinusTwoToTwo, fullRangeForType(kValuesTypes[u.type])))
+ .expand('value', u =>
+ unique(
+ minusTwoToTwoRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
const expectedResult =
- t.params.value >= 0 && isRepresentable(Math.sqrt(t.params.value), elementType(type));
+ t.params.value >= 0 &&
+ isRepresentable(
+ Math.sqrt(Number(t.params.value)),
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
validateConstOrOverrideBuiltinEval(
t,
builtin,
@@ -60,7 +69,7 @@ Validates that constant evaluation and override evaluation of ${builtin}() input
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kIntegerArgumentTypes = objectsToRecord([Type.f32, ...kConcreteIntegerScalarsAndVectors]);
g.test('integer_argument')
.desc(
@@ -74,8 +83,41 @@ Validates that scalar and vector integer arguments are rejected by ${builtin}()
validateConstOrOverrideBuiltinEval(
t,
builtin,
- /* expectedResult */ type === TypeF32,
+ /* expectedResult */ type === Type.f32,
[type.create(1)],
'constant'
);
});
+
+const kArgCases = {
+ good: '(1.1)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_too_few: '()',
+ bad_too_many: '(1.0,2.0)',
+ // Bad value type for arg 0
+ bad_0i32: '(1i)',
+ bad_0u32: '(1u)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/step.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/step.spec.ts
new file mode 100644
index 0000000000..a10ef20c7b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/step.spec.ts
@@ -0,0 +1,108 @@
+const builtin = 'step';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() error on invalid inputs.
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('a', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ .expand('b', u => fullRangeForType(kValidArgumentTypes[u.type], 5))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValidArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const expectedResult = true;
+
+ const type = kValidArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.a), type.create(t.params.b)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(1.2, 2.3)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_1arg: '(1.2)',
+ bad_3arg: '(1.2, 2.3, 4.5)',
+ // Bad value for arg 0
+ bad_0bool: '(false, 2.3)',
+ bad_0array: '(array(1.1,2.2), 2.3)',
+ bad_0struct: '(modf(2.2), 2.3)',
+ bad_0uint: '(1u, 2.3)',
+ bad_0int: '(1i, 2.3)',
+ bad_0vec2i: '(vec2i(), 2.3)',
+ bad_0vec2u: '(vec2u(), 2.3)',
+ bad_0vec3i: '(vec3i(), 2.3)',
+ bad_0vec3u: '(vec3u(), 2.3)',
+ bad_0vec4i: '(vec4i(), 2.3)',
+ bad_0vec4u: '(vec4u(), 2.3)',
+ // Bad value for arg 1
+ bad_1bool: '(1.2, false)',
+ bad_1array: '(1.2, array(1.1,2.2))',
+ bad_1struct: '(1.2, modf(2.2))',
+ bad_1uint: '(1.2, 1u)',
+ bad_1int: '(1.2, 1i)',
+ bad_1vec2i: '(1.2, vec2i())',
+ bad_1vec2u: '(1.2, vec2u())',
+ bad_1vec3i: '(1.2, vec3i())',
+ bad_1vec3u: '(1.2, vec3u())',
+ bad_1vec4i: '(1.2, vec4i())',
+ bad_1vec4u: '(1.2, vec4u())',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/tan.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/tan.spec.ts
index b9744643f6..9384585dd5 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/tan.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/tan.spec.ts
@@ -6,11 +6,9 @@ Validation tests for the ${builtin}() builtin.
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
import {
- TypeF16,
- TypeF32,
- elementType,
- kAllFloatScalarsAndVectors,
- kAllIntegerScalarsAndVectors,
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
} from '../../../../../util/conversion.js';
import { fpTraitsFor } from '../../../../../util/floating_point.js';
import { ShaderValidationTest } from '../../../shader_validation_test.js';
@@ -18,7 +16,7 @@ import { ShaderValidationTest } from '../../../shader_validation_test.js';
import {
fullRangeForType,
kConstantAndOverrideStages,
- kMinus3PiTo3Pi,
+ minusThreePiToThreePiRangeForType,
stageSupportsType,
unique,
validateConstOrOverrideBuiltinEval,
@@ -26,7 +24,7 @@ import {
export const g = makeTestGroup(ShaderValidationTest);
-const kValuesTypes = objectsToRecord(kAllFloatScalarsAndVectors);
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
g.test('values')
.desc(
@@ -40,18 +38,26 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
.combine('type', keysOf(kValuesTypes))
.filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
.beginSubcases()
- .expand('value', u => unique(kMinus3PiTo3Pi, fullRangeForType(kValuesTypes[u.type])))
+ .expand('value', u =>
+ unique(
+ minusThreePiToThreePiRangeForType(kValuesTypes[u.type]),
+ fullRangeForType(kValuesTypes[u.type])
+ )
+ )
)
.beforeAllSubcases(t => {
- if (elementType(kValuesTypes[t.params.type]) === TypeF16) {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
t.selectDeviceOrSkipTestCase('shader-f16');
}
})
.fn(t => {
const type = kValuesTypes[t.params.type];
- const fp = fpTraitsFor(elementType(type));
+ const fp = fpTraitsFor(
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
const smallestPositive = fp.constants().positive.min;
- const v = fp.quantize(t.params.value);
+ const v = fp.quantize(Number(t.params.value));
const expectedResult = Math.abs(Math.cos(v)) > smallestPositive;
validateConstOrOverrideBuiltinEval(
t,
@@ -62,22 +68,40 @@ Validates that constant evaluation and override evaluation of ${builtin}() rejec
);
});
-const kIntegerArgumentTypes = objectsToRecord([TypeF32, ...kAllIntegerScalarsAndVectors]);
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
-g.test('integer_argument')
- .desc(
- `
-Validates that scalar and vector integer arguments are rejected by ${builtin}()
-`
- )
- .params(u => u.combine('type', keysOf(kIntegerArgumentTypes)))
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
.fn(t => {
- const type = kIntegerArgumentTypes[t.params.type];
- validateConstOrOverrideBuiltinEval(
- t,
- builtin,
- /* expectedResult */ type === TypeF32,
- [type.create(0)],
- 'constant'
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
);
});
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/tanh.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/tanh.spec.ts
new file mode 100644
index 0000000000..965eb85111
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/tanh.spec.ts
@@ -0,0 +1,98 @@
+const builtin = 'tanh';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { isRepresentable } from '../../../../../util/floating_point.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValuesTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() rejects invalid values
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValuesTypes))
+ .filter(u => stageSupportsType(u.stage, kValuesTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValuesTypes[u.type]))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValuesTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kValuesTypes[t.params.type];
+ const expectedResult = isRepresentable(
+ Math.tanh(Number(t.params.value)),
+ // AbstractInt is converted to AbstractFloat before calling into the builtin
+ scalarTypeOf(type).kind === 'abstract-int' ? Type.abstractFloat : scalarTypeOf(type)
+ );
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureGather.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureGather.spec.ts
new file mode 100644
index 0000000000..35a2be1d48
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureGather.spec.ts
@@ -0,0 +1,335 @@
+const builtin = 'textureGather';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureGather component parameter must be correct type
+* test textureGather component parameter must be between 0 and 3 inclusive
+* test textureGather component parameter must be a const expression
+* test textureGather coords parameter must be correct type
+* test textureGather array_index parameter must be correct type
+* test textureGather offset parameter must be correct type
+* test textureGather offset parameter must be a const-expression
+* test textureGather offset parameter must be between -8 and +7 inclusive
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+type TextureGatherArguments = {
+ hasComponentArg?: boolean;
+ coordsArgType: ScalarType | VectorType;
+ hasArrayIndexArg?: boolean;
+ offsetArgType?: VectorType;
+};
+
+const kValidTextureGatherParameterTypes: { [n: string]: TextureGatherArguments } = {
+ 'texture_2d<f32>': {
+ hasComponentArg: true,
+ coordsArgType: Type.vec2f,
+ offsetArgType: Type.vec2i,
+ },
+ 'texture_2d_array<f32>': {
+ hasComponentArg: true,
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ 'texture_cube<f32>': { hasComponentArg: true, coordsArgType: Type.vec3f },
+ 'texture_cube_array<f32>': {
+ hasComponentArg: true,
+ coordsArgType: Type.vec3f,
+ hasArrayIndexArg: true,
+ },
+ texture_depth_2d: { coordsArgType: Type.vec2f, offsetArgType: Type.vec2i },
+ texture_depth_2d_array: {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ texture_depth_cube: { coordsArgType: Type.vec3f },
+ texture_depth_cube_array: { coordsArgType: Type.vec3f, hasArrayIndexArg: true },
+} as const;
+
+const kTextureTypes = keysOf(kValidTextureGatherParameterTypes);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('component_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegather')
+ .desc(
+ `
+Validates that only incorrect components arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureGatherParameterTypes))
+ // filter out types with no component argument
+ .filter(t => !!kValidTextureGatherParameterTypes[t.textureType].hasComponentArg)
+ .combine('componentType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1, 2, 3, 4] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.componentType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureGatherParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, componentType, offset, value } = t.params;
+ const componentArgType = kValuesTypes[componentType];
+ const { offsetArgType, coordsArgType, hasArrayIndexArg } =
+ kValidTextureGatherParameterTypes[textureType];
+
+ const componentWGSL = componentArgType.create(value).wgsl();
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureGather(${componentWGSL}, t, s, ${coordWGSL}${arrayWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ (isConvertible(componentArgType, Type.i32) || isConvertible(componentArgType, Type.u32)) &&
+ value >= 0 &&
+ value <= 3;
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('component_argument,non_const')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegather')
+ .desc(
+ `
+Validates that only non-const components arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureGatherParameterTypes))
+ // filter out types with no component argument
+ .filter(t => !!kValidTextureGatherParameterTypes[t.textureType].hasComponentArg)
+ .combine('varType', ['c', 'u', 'l'])
+ .beginSubcases()
+ .expand('offset', t =>
+ kValidTextureGatherParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, varType, offset } = t.params;
+ const componentArgType = Type.u32;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureGatherParameterTypes[textureType];
+
+ const componentWGSL = `${componentArgType}(${varType})`;
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@group(0) @binding(2) var<uniform> u: ${componentArgType};
+
+@fragment fn fs() -> @location(0) vec4f {
+ const c = 1;
+ let l = 1;
+ let v = textureGather(${componentWGSL}, t, s, ${coordWGSL}${arrayWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = varType === 'c';
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegather')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureGatherParameterTypes))
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureGatherParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, coordType, offset, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const {
+ hasComponentArg,
+ offsetArgType,
+ coordsArgType: coordsRequiredType,
+ hasArrayIndexArg,
+ } = kValidTextureGatherParameterTypes[textureType];
+
+ const componentWGSL = hasComponentArg ? '0, ' : '';
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureGather(${componentWGSL}t, s, ${coordWGSL}${arrayWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(coordArgType, coordsRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegather')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no array_index
+ .filter(t => !!kValidTextureGatherParameterTypes[t.textureType].hasArrayIndexArg)
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureGatherParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, value, offset } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { hasComponentArg, coordsArgType, offsetArgType } =
+ kValidTextureGatherParameterTypes[textureType];
+
+ const componentWGSL = hasComponentArg ? '0, ' : '';
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureGather(${componentWGSL}t, s, ${coordWGSL}, ${arrayWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegather')
+ .desc(
+ `
+Validates that only incorrect offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no offset
+ .filter(t => !!kValidTextureGatherParameterTypes[t.textureType].offsetArgType)
+ .combine('offsetType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.offsetType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, offsetType, value } = t.params;
+ const offsetArgType = kValuesTypes[offsetType];
+ const args = [offsetArgType.create(value)];
+ const {
+ hasComponentArg,
+ coordsArgType,
+ hasArrayIndexArg,
+ offsetArgType: offsetRequiredType,
+ } = kValidTextureGatherParameterTypes[textureType];
+
+ const componentWGSL = hasComponentArg ? '0, ' : '';
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = args.map(arg => arg.wgsl()).join(', ');
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureGather(${componentWGSL}t, s, ${coordWGSL}${arrayWGSL}, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(offsetArgType, offsetRequiredType!) && value >= -8 && value <= 7;
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument,non_const')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegather')
+ .desc(
+ `
+Validates that only non-const offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('varType', ['c', 'u', 'l'])
+ // filter out types with no offset
+ .filter(t => !!kValidTextureGatherParameterTypes[t.textureType].offsetArgType)
+ )
+ .fn(t => {
+ const { textureType, varType } = t.params;
+ const { hasComponentArg, coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureGatherParameterTypes[textureType];
+
+ const componentWGSL = hasComponentArg ? '0, ' : '';
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = `${offsetArgType}(${varType})`;
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@group(0) @binding(2) var<uniform> u: ${offsetArgType};
+@fragment fn fs() -> @location(0) vec4f {
+ const c = 1;
+ let l = ${offsetArgType!.create(0).wgsl()};
+ let v = textureGather(${componentWGSL}t, s, ${coordWGSL}${arrayWGSL}, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = varType === 'c';
+ t.expectCompileResult(expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureGatherCompare.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureGatherCompare.spec.ts
new file mode 100644
index 0000000000..84ba2ad95d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureGatherCompare.spec.ts
@@ -0,0 +1,264 @@
+const builtin = 'textureGatherCompare';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureGatherCompare coords parameter must be correct type
+* test textureGatherCompare array_index parameter must be correct type
+* test textureGatherCompare depth_ref parameter must be correct type
+* test textureGatherCompare offset parameter must be correct type
+* test textureGatherCompare offset parameter must be a const-expression
+* test textureGatherCompare offset parameter must be between -8 and +7 inclusive
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+type TextureGatherCompareArguments = {
+ coordsArgType: ScalarType | VectorType;
+ hasArrayIndexArg?: boolean;
+ offsetArgType?: VectorType;
+};
+
+const kValidTextureGatherCompareParameterTypes: { [n: string]: TextureGatherCompareArguments } = {
+ texture_depth_2d: { coordsArgType: Type.vec2f, offsetArgType: Type.vec2i },
+ texture_depth_2d_array: {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ texture_depth_cube: { coordsArgType: Type.vec3f },
+ texture_depth_cube_array: { coordsArgType: Type.vec3f, hasArrayIndexArg: true },
+} as const;
+
+const kTextureTypes = keysOf(kValidTextureGatherCompareParameterTypes);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegathercompare')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureGatherCompareParameterTypes))
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureGatherCompareParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, coordType, offset, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const {
+ offsetArgType,
+ coordsArgType: coordsRequiredType,
+ hasArrayIndexArg,
+ } = kValidTextureGatherCompareParameterTypes[textureType];
+
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureGatherCompare(t, s, ${coordWGSL}${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(coordArgType, coordsRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegathercompare')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no array_index
+ .filter(t => !!kValidTextureGatherCompareParameterTypes[t.textureType].hasArrayIndexArg)
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureGatherCompareParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, value, offset } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgType, offsetArgType } = kValidTextureGatherCompareParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureGatherCompare(t, s, ${coordWGSL}, ${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('depth_ref_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegathercompare')
+ .desc(
+ `
+Validates that only incorrect depth_ref arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureGatherCompareParameterTypes))
+ .combine('depthRefType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.depthRefType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureGatherCompareParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, depthRefType, offset, value } = t.params;
+ const depthRefArgType = kValuesTypes[depthRefType];
+ const { offsetArgType, coordsArgType, hasArrayIndexArg } =
+ kValidTextureGatherCompareParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const depthRefWGSL = depthRefArgType.create(value).wgsl();
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureGatherCompare(t, s, ${coordWGSL}${arrayWGSL}, ${depthRefWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(depthRefArgType, Type.f32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegathercompare')
+ .desc(
+ `
+Validates that only incorrect offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no offset
+ .filter(t => !!kValidTextureGatherCompareParameterTypes[t.textureType].offsetArgType)
+ .combine('offsetType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.offsetType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, offsetType, value } = t.params;
+ const offsetArgType = kValuesTypes[offsetType];
+ const args = [offsetArgType.create(value)];
+ const {
+ coordsArgType,
+ hasArrayIndexArg,
+ offsetArgType: offsetRequiredType,
+ } = kValidTextureGatherCompareParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = args.map(arg => arg.wgsl()).join(', ');
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureGatherCompare(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(offsetArgType, offsetRequiredType!) && value >= -8 && value <= 7;
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument,non_const')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturegathercompare')
+ .desc(
+ `
+Validates that only non-const offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('varType', ['c', 'u', 'l'])
+ // filter out types with no offset
+ .filter(t => !!kValidTextureGatherCompareParameterTypes[t.textureType].offsetArgType)
+ )
+ .fn(t => {
+ const { textureType, varType } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureGatherCompareParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = `${offsetArgType}(${varType})`;
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@group(0) @binding(2) var<uniform> u: ${offsetArgType};
+@fragment fn fs() -> @location(0) vec4f {
+ const c = 1;
+ let l = ${offsetArgType!.create(0).wgsl()};
+ let v = textureGatherCompare(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = varType === 'c';
+ t.expectCompileResult(expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureLoad.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureLoad.spec.ts
new file mode 100644
index 0000000000..9138b2ecc6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureLoad.spec.ts
@@ -0,0 +1,370 @@
+const builtin = 'textureLoad';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureLoad coords parameter must be correct type
+* test textureLoad array_index parameter must be correct type
+* test textureLoad level parameter must be correct type
+* test textureLoad sample_index parameter must be correct type
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import { assert } from '../../../../../../common/util/util.js';
+import { kAllTextureFormats, kTextureFormatInfo } from '../../../../../format_info.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+type TextureLoadArguments = {
+ coordsArgTypes: readonly [ScalarType | VectorType, ScalarType | VectorType];
+ usesMultipleTypes?: boolean; // texture can use f32, i32, u32
+ hasArrayIndexArg?: boolean;
+ hasLevelArg?: boolean;
+ hasSampleIndexArg?: boolean;
+};
+
+const kCoords1DTypes = [Type.i32, Type.u32] as const;
+const kCoords2DTypes = [Type.vec2i, Type.vec2u] as const;
+const kCoords3DTypes = [Type.vec3i, Type.vec3u] as const;
+
+const kValidTextureLoadParameterTypesForNonStorageTextures: { [n: string]: TextureLoadArguments } =
+ {
+ texture_1d: { usesMultipleTypes: true, coordsArgTypes: kCoords1DTypes, hasLevelArg: true },
+ texture_2d: { usesMultipleTypes: true, coordsArgTypes: kCoords2DTypes, hasLevelArg: true },
+ texture_2d_array: {
+ usesMultipleTypes: true,
+ coordsArgTypes: kCoords2DTypes,
+ hasArrayIndexArg: true,
+ hasLevelArg: true,
+ },
+ texture_3d: { usesMultipleTypes: true, coordsArgTypes: kCoords3DTypes, hasLevelArg: true },
+ texture_multisampled_2d: {
+ usesMultipleTypes: true,
+ coordsArgTypes: kCoords2DTypes,
+ hasSampleIndexArg: true,
+ },
+ texture_depth_2d: { coordsArgTypes: kCoords2DTypes, hasLevelArg: true },
+ texture_depth_2d_array: {
+ coordsArgTypes: kCoords2DTypes,
+ hasArrayIndexArg: true,
+ hasLevelArg: true,
+ },
+ texture_depth_multisampled_2d: { coordsArgTypes: kCoords2DTypes, hasSampleIndexArg: true },
+ texture_external: { coordsArgTypes: kCoords2DTypes },
+ };
+
+const kValidTextureLoadParameterTypesForStorageTextures: { [n: string]: TextureLoadArguments } = {
+ texture_storage_1d: { coordsArgTypes: [Type.i32, Type.u32] },
+ texture_storage_2d: { coordsArgTypes: [Type.vec2i, Type.vec2u] },
+ texture_storage_2d_array: {
+ coordsArgTypes: [Type.vec2i, Type.vec2u],
+ hasArrayIndexArg: true,
+ },
+ texture_storage_3d: { coordsArgTypes: [Type.vec3i, Type.vec3u] },
+} as const;
+
+const kNonStorageTextureTypes = keysOf(kValidTextureLoadParameterTypesForNonStorageTextures);
+const kStorageTextureTypes = keysOf(kValidTextureLoadParameterTypesForStorageTextures);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+const kTexelType: { [n: string]: Type } = {
+ f32: Type.vec4f,
+ i32: Type.vec4i,
+ u32: Type.vec4u,
+} as const;
+
+const kTexelTypes = keysOf(kTexelType);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument,non_storage')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#textureload')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kNonStorageTextureTypes)
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .expand('texelType', t =>
+ kValidTextureLoadParameterTypesForNonStorageTextures[t.textureType].usesMultipleTypes
+ ? kTexelTypes
+ : ['']
+ )
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, coordType, texelType, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const { coordsArgTypes, hasArrayIndexArg, hasLevelArg, hasSampleIndexArg } =
+ kValidTextureLoadParameterTypesForNonStorageTextures[textureType];
+
+ const texelTypeWGSL = texelType ? `<${texelType}>` : '';
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const levelWGSL = hasLevelArg ? ', 0' : '';
+ const sampleIndexWGSL = hasSampleIndexArg ? ', 0' : '';
+
+ const code = `
+@group(0) @binding(0) var t: ${textureType}${texelTypeWGSL};
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureLoad(t, ${coordWGSL}${arrayWGSL}${levelWGSL}${sampleIndexWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(coordArgType, coordsArgTypes[0]) ||
+ isConvertible(coordArgType, coordsArgTypes[1]);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('coords_argument,storage')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#textureload')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kStorageTextureTypes)
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('format', kAllTextureFormats)
+ // filter to only storage texture formats.
+ .filter(t => !!kTextureFormatInfo[t.format].color?.storage)
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ )
+ .beforeAllSubcases(t =>
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures')
+ )
+ .fn(t => {
+ const { textureType, coordType, format, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const { coordsArgTypes, hasArrayIndexArg } =
+ kValidTextureLoadParameterTypesForStorageTextures[textureType];
+
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+
+ const code = `
+@group(0) @binding(0) var t: ${textureType}<${format}, read>;
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureLoad(t, ${coordWGSL}${arrayWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(coordArgType, coordsArgTypes[0]) ||
+ isConvertible(coordArgType, coordsArgTypes[1]);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument,non_storage')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#textureload')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kNonStorageTextureTypes)
+ // filter out types with no array_index
+ .filter(
+ t => !!kValidTextureLoadParameterTypesForNonStorageTextures[t.textureType].hasArrayIndexArg
+ )
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .expand('texelType', t =>
+ kValidTextureLoadParameterTypesForNonStorageTextures[t.textureType].usesMultipleTypes
+ ? kTexelTypes
+ : ['']
+ )
+ .combine('value', [-1, 0, 1])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, texelType, value } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgTypes, hasLevelArg } =
+ kValidTextureLoadParameterTypesForNonStorageTextures[textureType];
+
+ const texelTypeWGSL = texelType ? `<${texelType}>` : '';
+ const coordWGSL = coordsArgTypes[0].create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const levelWGSL = hasLevelArg ? ', 0' : '';
+
+ const code = `
+@group(0) @binding(0) var t: ${textureType}${texelTypeWGSL};
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureLoad(t, ${coordWGSL}, ${arrayWGSL}${levelWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument,storage')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#textureload')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kStorageTextureTypes)
+ // filter out types with no array_index
+ .filter(
+ t => !!kValidTextureLoadParameterTypesForStorageTextures[t.textureType].hasArrayIndexArg
+ )
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('format', kAllTextureFormats)
+ // filter to only storage texture formats.
+ .filter(t => !!kTextureFormatInfo[t.format].color?.storage)
+ .combine('value', [-1, 0, 1])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ )
+ .beforeAllSubcases(t =>
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures')
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, format, value } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgTypes, hasLevelArg } =
+ kValidTextureLoadParameterTypesForStorageTextures[textureType];
+
+ const coordWGSL = coordsArgTypes[0].create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const levelWGSL = hasLevelArg ? ', 0' : '';
+
+ const code = `
+@group(0) @binding(0) var t: ${textureType}<${format}, read>;
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureLoad(t, ${coordWGSL}, ${arrayWGSL}${levelWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('level_argument,non_storage')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#textureload')
+ .desc(
+ `
+Validates that only incorrect level arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kNonStorageTextureTypes)
+ // filter out types with no level
+ .filter(
+ t => !!kValidTextureLoadParameterTypesForNonStorageTextures[t.textureType].hasLevelArg
+ )
+ .combine('levelType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .expand('texelType', t =>
+ kValidTextureLoadParameterTypesForNonStorageTextures[t.textureType].usesMultipleTypes
+ ? kTexelTypes
+ : ['']
+ )
+ .combine('value', [-1, 0, 1])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.levelType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, levelType, texelType, value } = t.params;
+ const levelArgType = kValuesTypes[levelType];
+ const { coordsArgTypes, hasArrayIndexArg } =
+ kValidTextureLoadParameterTypesForNonStorageTextures[textureType];
+
+ const texelTypeWGSL = texelType ? `<${texelType}>` : '';
+ const coordWGSL = coordsArgTypes[0].create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const levelWGSL = levelArgType.create(value).wgsl();
+
+ const code = `
+@group(0) @binding(0) var t: ${textureType}${texelTypeWGSL};
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureLoad(t, ${coordWGSL}${arrayWGSL}, ${levelWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(levelArgType, Type.i32) || isConvertible(levelArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('sample_index_argument,non_storage')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#textureload')
+ .desc(
+ `
+Validates that only incorrect sample_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kNonStorageTextureTypes)
+ // filter out types with no sample_index
+ .filter(
+ t => !!kValidTextureLoadParameterTypesForNonStorageTextures[t.textureType].hasSampleIndexArg
+ )
+ .combine('sampleIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .expand('texelType', t =>
+ kValidTextureLoadParameterTypesForNonStorageTextures[t.textureType].usesMultipleTypes
+ ? kTexelTypes
+ : ['']
+ )
+ .combine('value', [-1, 0, 1])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.sampleIndexType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, sampleIndexType, texelType, value } = t.params;
+ const sampleIndexArgType = kValuesTypes[sampleIndexType];
+ const { coordsArgTypes, hasArrayIndexArg, hasLevelArg } =
+ kValidTextureLoadParameterTypesForNonStorageTextures[textureType];
+ assert(!hasLevelArg);
+
+ const texelTypeWGSL = texelType ? `<${texelType}>` : '';
+ const coordWGSL = coordsArgTypes[0].create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const sampleIndexWGSL = sampleIndexArgType.create(value).wgsl();
+
+ const code = `
+@group(0) @binding(0) var t: ${textureType}${texelTypeWGSL};
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureLoad(t, ${coordWGSL}${arrayWGSL}, ${sampleIndexWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(sampleIndexArgType, Type.i32) || isConvertible(sampleIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSample.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSample.spec.ts
new file mode 100644
index 0000000000..bdd3cbd8e8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSample.spec.ts
@@ -0,0 +1,267 @@
+const builtin = 'textureSample';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureSample coords parameter must be correct type
+* test textureSample array_index parameter must be correct type
+* test textureSample coords parameter must be correct type
+* test textureSample offset parameter must be correct type
+* test textureSample offset parameter must be a const-expression
+* test textureSample offset parameter must be between -8 and +7 inclusive
+* test textureSample not usable in a compute or vertex shader
+
+note: uniformity validation is covered in src/webgpu/shader/validation/uniformity/uniformity.spec.ts
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import { kEntryPointsToValidateFragmentOnlyBuiltins } from './shader_stage_utils.js';
+
+type TextureSampleArguments = {
+ coordsArgType: ScalarType | VectorType;
+ hasArrayIndexArg?: boolean;
+ offsetArgType?: VectorType;
+};
+
+const kValidTextureSampleParameterTypes: { [n: string]: TextureSampleArguments } = {
+ 'texture_1d<f32>': { coordsArgType: Type.f32 },
+ 'texture_2d<f32>': { coordsArgType: Type.vec2f, offsetArgType: Type.vec2i },
+ 'texture_2d_array<f32>': {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ 'texture_3d<f32>': { coordsArgType: Type.vec3f, offsetArgType: Type.vec3i },
+ 'texture_cube<f32>': { coordsArgType: Type.vec3f },
+ 'texture_cube_array<f32>': { coordsArgType: Type.vec3f, hasArrayIndexArg: true },
+ texture_depth_2d: { coordsArgType: Type.vec2f, offsetArgType: Type.vec2i },
+ texture_depth_2d_array: {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ texture_depth_cube: { coordsArgType: Type.vec3f },
+ texture_depth_cube_array: { coordsArgType: Type.vec3f, hasArrayIndexArg: true },
+} as const;
+
+const kTextureTypes = keysOf(kValidTextureSampleParameterTypes);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesample')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureSampleParameterTypes))
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, coordType, offset, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const {
+ offsetArgType,
+ coordsArgType: coordsRequiredType,
+ hasArrayIndexArg,
+ } = kValidTextureSampleParameterTypes[textureType];
+
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSample(t, s, ${coordWGSL}${arrayWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(coordArgType, coordsRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesample')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no array_index
+ .filter(t => !!kValidTextureSampleParameterTypes[t.textureType].hasArrayIndexArg)
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, value, offset } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgType, offsetArgType } = kValidTextureSampleParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSample(t, s, ${coordWGSL}, ${arrayWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesample')
+ .desc(
+ `
+Validates that only incorrect offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleParameterTypes[t.textureType].offsetArgType)
+ .combine('offsetType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.offsetType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, offsetType, value } = t.params;
+ const offsetArgType = kValuesTypes[offsetType];
+ const args = [offsetArgType.create(value)];
+ const {
+ coordsArgType,
+ hasArrayIndexArg,
+ offsetArgType: offsetRequiredType,
+ } = kValidTextureSampleParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = args.map(arg => arg.wgsl()).join(', ');
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSample(t, s, ${coordWGSL}${arrayWGSL}, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(offsetArgType, offsetRequiredType!) && value >= -8 && value <= 7;
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument,non_const')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesample')
+ .desc(
+ `
+Validates that only non-const offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('varType', ['c', 'u', 'l'])
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleParameterTypes[t.textureType].offsetArgType)
+ )
+ .fn(t => {
+ const { textureType, varType } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = `${offsetArgType}(${varType})`;
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@group(0) @binding(2) var<uniform> u: ${offsetArgType};
+@fragment fn fs() -> @location(0) vec4f {
+ const c = 1;
+ let l = ${offsetArgType!.create(0).wgsl()};
+ let v = textureSample(t, s, ${coordWGSL}${arrayWGSL}, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = varType === 'c';
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('only_in_fragment')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesample')
+ .desc(
+ `
+Validates that ${builtin} must not be used in a compute or vertex shader.
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('entryPoint', keysOf(kEntryPointsToValidateFragmentOnlyBuiltins))
+ .expand('offset', t =>
+ kValidTextureSampleParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, entryPoint, offset } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const config = kEntryPointsToValidateFragmentOnlyBuiltins[entryPoint];
+ const code = `
+${config.code}
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+
+fn foo() {
+ _ = textureSample(t, s, ${coordWGSL}${arrayWGSL}${offsetWGSL});
+}`;
+ t.expectCompileResult(config.expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleBaseClampToEdge.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleBaseClampToEdge.spec.ts
new file mode 100644
index 0000000000..4c3a7cb9b7
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleBaseClampToEdge.spec.ts
@@ -0,0 +1,54 @@
+const builtin = 'textureSampleBaseClampToEdge';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureSampleBaseClampToEdge coords parameter must be correct type
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kTextureSampleBaseClampToEdgeTextureTypes = ['texture_2d<f32>', 'texture_external'] as const;
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplebaseclamptoedge')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureSampleBaseClampToEdgeTextureTypes)
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, coordType, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const coordWGSL = coordArgType.create(value).wgsl();
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleBaseClampToEdge(t, s, ${coordWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(coordArgType, Type.vec2f);
+ t.expectCompileResult(expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleBias.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleBias.spec.ts
new file mode 100644
index 0000000000..549add20e1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleBias.spec.ts
@@ -0,0 +1,309 @@
+const builtin = 'textureSampleBias';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureSampleBias coords parameter must be correct type
+* test textureSampleBias array_index parameter must be correct type
+* test textureSampleBias bias parameter must be correct type
+* test textureSampleBias bias parameter must be between -16.0 and 15.99 inclusive if it's a constant
+* test textureSampleBias offset parameter must be correct type
+* test textureSampleBias offset parameter must be a const-expression
+* test textureSampleBias offset parameter must be between -8 and +7 inclusive
+
+note: uniformity validation is covered in src/webgpu/shader/validation/uniformity/uniformity.spec.ts
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+ scalarTypeOf,
+ isFloatType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import { kEntryPointsToValidateFragmentOnlyBuiltins } from './shader_stage_utils.js';
+
+type TextureSampleBiasArguments = {
+ coordsArgType: ScalarType | VectorType;
+ hasArrayIndexArg?: boolean;
+ offsetArgType?: VectorType;
+};
+
+const kValidTextureSampleBiasParameterTypes: { [n: string]: TextureSampleBiasArguments } = {
+ 'texture_2d<f32>': { coordsArgType: Type.vec2f, offsetArgType: Type.vec2i },
+ 'texture_2d_array<f32>': {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ 'texture_3d<f32>': { coordsArgType: Type.vec3f, offsetArgType: Type.vec3i },
+ 'texture_cube<f32>': { coordsArgType: Type.vec3f },
+ 'texture_cube_array<f32>': { coordsArgType: Type.vec3f, hasArrayIndexArg: true },
+} as const;
+
+const kTextureTypes = keysOf(kValidTextureSampleBiasParameterTypes);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplebias')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureSampleBiasParameterTypes))
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleBiasParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, coordType, offset, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const {
+ offsetArgType,
+ coordsArgType: coordsRequiredType,
+ hasArrayIndexArg,
+ } = kValidTextureSampleBiasParameterTypes[textureType];
+
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleBias(t, s, ${coordWGSL}${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(coordArgType, coordsRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplebias')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no array_index
+ .filter(t => !!kValidTextureSampleBiasParameterTypes[t.textureType].hasArrayIndexArg)
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleBiasParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, value, offset } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgType, offsetArgType } = kValidTextureSampleBiasParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleBias(t, s, ${coordWGSL}, ${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('bias_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplebias')
+ .desc(
+ `
+Validates that only incorrect bias arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleBiasParameterTypes[t.textureType].offsetArgType)
+ .combine('biasType', keysOf(kValuesTypes))
+ .beginSubcases()
+ // The spec mentions limits of > -16 and < 15.99 so pass some values around there
+ // No error is mentioned for out of range values so make sure no error is generated.
+ .combine('value', [-17, -16, -8, 0, 7, 15.99, 16])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.biasType]) || t.value >= 0)
+ // filter out non-integer values passed to integer types.
+ .filter(t => Number.isInteger(t.value) || isFloatType(scalarTypeOf(kValuesTypes[t.biasType])))
+ .expand('offset', t =>
+ kValidTextureSampleBiasParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, biasType, value, offset } = t.params;
+ const biasArgType = kValuesTypes[biasType];
+ const args = [biasArgType.create(value)];
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleBiasParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const biasWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleBias(t, s, ${coordWGSL}${arrayWGSL}, ${biasWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(biasArgType, Type.f32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplebias')
+ .desc(
+ `
+Validates that only incorrect offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleBiasParameterTypes[t.textureType].offsetArgType)
+ .combine('offsetType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.offsetType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, offsetType, value } = t.params;
+ const offsetArgType = kValuesTypes[offsetType];
+ const args = [offsetArgType.create(value)];
+ const {
+ coordsArgType,
+ hasArrayIndexArg,
+ offsetArgType: offsetRequiredType,
+ } = kValidTextureSampleBiasParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = args.map(arg => arg.wgsl()).join(', ');
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleBias(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(offsetArgType, offsetRequiredType!) && value >= -8 && value <= 7;
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument,non_const')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplebias')
+ .desc(
+ `
+Validates that only non-const offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('varType', ['c', 'u', 'l'])
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleBiasParameterTypes[t.textureType].offsetArgType)
+ )
+ .fn(t => {
+ const { textureType, varType } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleBiasParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = `${offsetArgType}(${varType})`;
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@group(0) @binding(2) var<uniform> u: ${offsetArgType};
+@fragment fn fs() -> @location(0) vec4f {
+ const c = 1;
+ let l = ${offsetArgType!.create(0).wgsl()};
+ let v = textureSampleBias(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = varType === 'c';
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('only_in_fragment')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesample')
+ .desc(
+ `
+Validates that ${builtin} must not be used in a compute or vertex shader.
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('entryPoint', keysOf(kEntryPointsToValidateFragmentOnlyBuiltins))
+ .expand('offset', t =>
+ kValidTextureSampleBiasParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, entryPoint, offset } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleBiasParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const config = kEntryPointsToValidateFragmentOnlyBuiltins[entryPoint];
+ const code = `
+${config.code}
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+
+fn foo() {
+ _ = textureSampleBias(t, s, ${coordWGSL}${arrayWGSL}, 0${offsetWGSL});
+}`;
+ t.expectCompileResult(config.expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleCompare.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleCompare.spec.ts
new file mode 100644
index 0000000000..9c07d0354a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleCompare.spec.ts
@@ -0,0 +1,308 @@
+const builtin = 'textureSampleCompare';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureSampleCompare coords parameter must be correct type
+* test textureSampleCompare array_index parameter must be correct type
+* test textureSampleCompare depth_ref parameter must be correct type
+* test textureSampleCompare offset parameter must be correct type
+* test textureSampleCompare offset parameter must be a const-expression
+* test textureSampleCompare offset parameter must be between -8 and +7 inclusive
+* test textureSample not usable in a compute or vertex shader
+
+note: uniformity validation is covered in src/webgpu/shader/validation/uniformity/uniformity.spec.ts
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import { kEntryPointsToValidateFragmentOnlyBuiltins } from './shader_stage_utils.js';
+
+type TextureSampleCompareArguments = {
+ coordsArgType: ScalarType | VectorType;
+ hasArrayIndexArg?: boolean;
+ offsetArgType?: VectorType;
+};
+
+const kValidTextureSampleCompareParameterTypes: { [n: string]: TextureSampleCompareArguments } = {
+ texture_depth_2d: { coordsArgType: Type.vec2f, offsetArgType: Type.vec2i },
+ texture_depth_2d_array: {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ texture_depth_cube: { coordsArgType: Type.vec3f },
+ texture_depth_cube_array: { coordsArgType: Type.vec3f, hasArrayIndexArg: true },
+} as const;
+
+const kTextureTypes = keysOf(kValidTextureSampleCompareParameterTypes);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecompare')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureSampleCompareParameterTypes))
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleCompareParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, coordType, offset, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const {
+ offsetArgType,
+ coordsArgType: coordsRequiredType,
+ hasArrayIndexArg,
+ } = kValidTextureSampleCompareParameterTypes[textureType];
+
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureSampleCompare(t, s, ${coordWGSL}${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(coordArgType, coordsRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecompare')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no array_index
+ .filter(t => !!kValidTextureSampleCompareParameterTypes[t.textureType].hasArrayIndexArg)
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleCompareParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, value, offset } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgType, offsetArgType } = kValidTextureSampleCompareParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureSampleCompare(t, s, ${coordWGSL}, ${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('depth_ref_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecompare')
+ .desc(
+ `
+Validates that only incorrect depth_ref arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('depthRefType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.depthRefType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleCompareParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, depthRefType, value, offset } = t.params;
+ const depthRefArgType = kValuesTypes[depthRefType];
+ const args = [depthRefArgType.create(value)];
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleCompareParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const depthRefWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureSampleCompare(t, s, ${coordWGSL}${arrayWGSL}, ${depthRefWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(depthRefArgType, Type.f32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecompare')
+ .desc(
+ `
+Validates that only incorrect offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleCompareParameterTypes[t.textureType].offsetArgType)
+ .combine('offsetType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.offsetType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, offsetType, value } = t.params;
+ const offsetArgType = kValuesTypes[offsetType];
+ const args = [offsetArgType.create(value)];
+ const {
+ coordsArgType,
+ hasArrayIndexArg,
+ offsetArgType: offsetRequiredType,
+ } = kValidTextureSampleCompareParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = args.map(arg => arg.wgsl()).join(', ');
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ _ = textureSampleCompare(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(offsetArgType, offsetRequiredType!) && value >= -8 && value <= 7;
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument,non_const')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecompare')
+ .desc(
+ `
+Validates that only non-const offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('varType', ['c', 'u', 'l'])
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleCompareParameterTypes[t.textureType].offsetArgType)
+ )
+ .fn(t => {
+ const { textureType, varType } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleCompareParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = `${offsetArgType}(${varType})`;
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@group(0) @binding(2) var<uniform> u: ${offsetArgType};
+@fragment fn fs() -> @location(0) vec4f {
+ const c = 1;
+ let l = ${offsetArgType?.create(0).wgsl()};
+ _ = textureSampleCompare(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = varType === 'c';
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('only_in_fragment')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesample')
+ .desc(
+ `
+Validates that ${builtin} must not be used in a compute or vertex shader.
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('entryPoint', keysOf(kEntryPointsToValidateFragmentOnlyBuiltins))
+ .expand('offset', t =>
+ kValidTextureSampleCompareParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, entryPoint, offset } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleCompareParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const config = kEntryPointsToValidateFragmentOnlyBuiltins[entryPoint];
+ const code = `
+${config.code}
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+
+fn foo() {
+ _ = textureSampleCompare(t, s, ${coordWGSL}${arrayWGSL}, 0${offsetWGSL});
+}`;
+ t.expectCompileResult(config.expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleCompareLevel.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleCompareLevel.spec.ts
new file mode 100644
index 0000000000..cfb2090dc7
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleCompareLevel.spec.ts
@@ -0,0 +1,268 @@
+const builtin = 'textureSampleCompareLevel';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureSampleCompareLevel coords parameter must be correct type
+* test textureSampleCompareLevel array_index parameter must be correct type
+* test textureSampleCompareLevel depth_ref parameter must be correct type
+* test textureSampleCompareLevel offset parameter must be correct type
+* test textureSampleCompareLevel offset parameter must be a const-expression
+* test textureSampleCompareLevel offset parameter must be between -8 and +7 inclusive
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+type TextureSampleCompareLevelArguments = {
+ coordsArgType: ScalarType | VectorType;
+ hasArrayIndexArg?: boolean;
+ offsetArgType?: VectorType;
+};
+
+const kValidTextureSampleCompareLevelParameterTypes: {
+ [n: string]: TextureSampleCompareLevelArguments;
+} = {
+ texture_depth_2d: { coordsArgType: Type.vec2f, offsetArgType: Type.vec2i },
+ texture_depth_2d_array: {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ texture_depth_cube: { coordsArgType: Type.vec3f },
+ texture_depth_cube_array: { coordsArgType: Type.vec3f, hasArrayIndexArg: true },
+} as const;
+
+const kTextureTypes = keysOf(kValidTextureSampleCompareLevelParameterTypes);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecomparelevel')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureSampleCompareLevelParameterTypes))
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleCompareLevelParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, coordType, offset, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const {
+ offsetArgType,
+ coordsArgType: coordsRequiredType,
+ hasArrayIndexArg,
+ } = kValidTextureSampleCompareLevelParameterTypes[textureType];
+
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleCompareLevel(t, s, ${coordWGSL}${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(coordArgType, coordsRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecomparelevel')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no array_index
+ .filter(t => !!kValidTextureSampleCompareLevelParameterTypes[t.textureType].hasArrayIndexArg)
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleCompareLevelParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, value, offset } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgType, offsetArgType } =
+ kValidTextureSampleCompareLevelParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleCompareLevel(t, s, ${coordWGSL}, ${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('depth_ref_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecomparelevel')
+ .desc(
+ `
+Validates that only incorrect depth_ref arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('depthRefType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.depthRefType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleCompareLevelParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, depthRefType, value, offset } = t.params;
+ const depthRefArgType = kValuesTypes[depthRefType];
+ const args = [depthRefArgType.create(value)];
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleCompareLevelParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const depthRefWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleCompareLevel(t, s, ${coordWGSL}${arrayWGSL}, ${depthRefWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(depthRefArgType, Type.f32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecomparelevel')
+ .desc(
+ `
+Validates that only incorrect offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleCompareLevelParameterTypes[t.textureType].offsetArgType)
+ .combine('offsetType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.offsetType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, offsetType, value } = t.params;
+ const offsetArgType = kValuesTypes[offsetType];
+ const args = [offsetArgType.create(value)];
+ const {
+ coordsArgType,
+ hasArrayIndexArg,
+ offsetArgType: offsetRequiredType,
+ } = kValidTextureSampleCompareLevelParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = args.map(arg => arg.wgsl()).join(', ');
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleCompareLevel(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(offsetArgType, offsetRequiredType!) && value >= -8 && value <= 7;
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument,non_const')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplecomparelevel')
+ .desc(
+ `
+Validates that only non-const offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('varType', ['c', 'u', 'l'])
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleCompareLevelParameterTypes[t.textureType].offsetArgType)
+ )
+ .fn(t => {
+ const { textureType, varType } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleCompareLevelParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = `${offsetArgType}(${varType})`;
+
+ const code = `
+@group(0) @binding(0) var s: sampler_comparison;
+@group(0) @binding(1) var t: ${textureType};
+@group(0) @binding(2) var<uniform> u: ${offsetArgType};
+@fragment fn fs() -> @location(0) vec4f {
+ const c = 1;
+ let l = ${offsetArgType?.create(0).wgsl()};
+ let v = textureSampleCompareLevel(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = varType === 'c';
+ t.expectCompileResult(expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleGrad.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleGrad.spec.ts
new file mode 100644
index 0000000000..3d1b522f86
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleGrad.spec.ts
@@ -0,0 +1,317 @@
+const builtin = 'textureSampleGrad';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureSampleGrad coords parameter must be correct type
+* test textureSampleGrad array_index parameter must be correct type
+* test textureSampleGrad ddX parameter must be correct type
+* test textureSampleGrad ddY parameter must be correct type
+* test textureSampleGrad coords parameter must be correct type
+* test textureSampleGrad offset parameter must be correct type
+* test textureSampleGrad offset parameter must be a const-expression
+* test textureSampleGrad offset parameter must be between -8 and +7 inclusive
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+// Note: ddX and ddy parameter types match coords so we'll use coordsArgType for ddX and ddY.
+type TextureSampleGradArguments = {
+ coordsArgType: ScalarType | VectorType;
+ hasArrayIndexArg?: boolean;
+ offsetArgType?: VectorType;
+};
+
+const kValidTextureSampleGradParameterTypes: { [n: string]: TextureSampleGradArguments } = {
+ 'texture_2d<f32>': {
+ coordsArgType: Type.vec2f,
+ offsetArgType: Type.vec2i,
+ },
+ 'texture_2d_array<f32>': {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ 'texture_3d<f32>': { coordsArgType: Type.vec3f, offsetArgType: Type.vec3i },
+ 'texture_cube<f32>': { coordsArgType: Type.vec3f },
+ 'texture_cube_array<f32>': { coordsArgType: Type.vec3f, hasArrayIndexArg: true },
+} as const;
+
+const kTextureTypes = keysOf(kValidTextureSampleGradParameterTypes);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplegrad')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureSampleGradParameterTypes))
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleGradParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, coordType, offset, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const {
+ offsetArgType,
+ coordsArgType: coordsRequiredType,
+ hasArrayIndexArg,
+ } = kValidTextureSampleGradParameterTypes[textureType];
+
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const ddWGSL = coordsRequiredType.create(0).wgsl();
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleGrad(t, s, ${coordWGSL}${arrayWGSL}, ${ddWGSL}, ${ddWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(coordArgType, coordsRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplegrad')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no array_index
+ .filter(t => !!kValidTextureSampleGradParameterTypes[t.textureType].hasArrayIndexArg)
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleGradParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, value, offset } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgType, offsetArgType } = kValidTextureSampleGradParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const ddWGSL = coordsArgType.create(0).wgsl();
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleGrad(t, s, ${coordWGSL}, ${arrayWGSL}, ${ddWGSL}, ${ddWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('ddX_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplegrad')
+ .desc(
+ `
+Validates that only incorrect ddX arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('ddxType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.ddxType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleGradParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, ddxType, value, offset } = t.params;
+ const ddxArgType = kValuesTypes[ddxType];
+ const args = [ddxArgType.create(value)];
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleGradParameterTypes[textureType];
+
+ const ddxRequiredType = coordsArgType;
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const ddXWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const ddYWGSL = coordsArgType.create(0).wgsl();
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleGrad(t, s, ${coordWGSL}${arrayWGSL}, ${ddXWGSL}, ${ddYWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(ddxArgType, ddxRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('ddY_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplegrad')
+ .desc(
+ `
+Validates that only incorrect ddY arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('ddyType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.ddyType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleGradParameterTypes[t.textureType].offsetArgType ? [false, true] : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, ddyType, value, offset } = t.params;
+ const ddyArgType = kValuesTypes[ddyType];
+ const args = [ddyArgType.create(value)];
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleGradParameterTypes[textureType];
+
+ const ddyRequiredType = coordsArgType;
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const ddXWGSL = coordsArgType.create(0).wgsl();
+ const ddYWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleGrad(t, s, ${coordWGSL}${arrayWGSL}, ${ddXWGSL}, ${ddYWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(ddyArgType, ddyRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplegrad')
+ .desc(
+ `
+Validates that only incorrect offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleGradParameterTypes[t.textureType].offsetArgType)
+ .combine('offsetType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.offsetType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, offsetType, value } = t.params;
+ const offsetArgType = kValuesTypes[offsetType];
+ const args = [offsetArgType.create(value)];
+ const {
+ coordsArgType,
+ hasArrayIndexArg,
+ offsetArgType: offsetRequiredType,
+ } = kValidTextureSampleGradParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const ddWGSL = coordsArgType.create(0).wgsl();
+ const offsetWGSL = args.map(arg => arg.wgsl()).join(', ');
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleGrad(t, s, ${coordWGSL}${arrayWGSL}, ${ddWGSL}, ${ddWGSL}, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(offsetArgType, offsetRequiredType!) && value >= -8 && value <= 7;
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument,non_const')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplegrad')
+ .desc(
+ `
+Validates that only non-const offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('varType', ['c', 'u', 'l'])
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleGradParameterTypes[t.textureType].offsetArgType)
+ )
+ .fn(t => {
+ const { textureType, varType } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleGradParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const ddWGSL = coordsArgType.create(0).wgsl();
+ const offsetWGSL = `${offsetArgType}(${varType})`;
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@group(0) @binding(2) var<uniform> u: ${offsetArgType};
+@fragment fn fs() -> @location(0) vec4f {
+ const c = 1;
+ let l = ${offsetArgType!.create(0).wgsl()};
+ let v = textureSampleGrad(t, s, ${coordWGSL}${arrayWGSL}, ${ddWGSL}, ${ddWGSL}, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = varType === 'c';
+ t.expectCompileResult(expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleLevel.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleLevel.spec.ts
new file mode 100644
index 0000000000..9a6701421c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureSampleLevel.spec.ts
@@ -0,0 +1,282 @@
+const builtin = 'textureSampleLevel';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureSampleLevel coords parameter must be correct type
+* test textureSampleLevel array_index parameter must be correct type
+* test textureSampleLevel level parameter must be correct type
+* test textureSampleLevel offset parameter must be correct type
+* test textureSampleLevel offset parameter must be a const-expression
+* test textureSampleLevel offset parameter must be between -8 and +7 inclusive
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+type TextureSampleLevelArguments = {
+ coordsArgType: ScalarType | VectorType;
+ hasArrayIndexArg?: boolean;
+ levelIsF32?: boolean;
+ offsetArgType?: VectorType;
+};
+
+const kValidTextureSampleLevelParameterTypes: { [n: string]: TextureSampleLevelArguments } = {
+ 'texture_2d<f32>': { coordsArgType: Type.vec2f, levelIsF32: true, offsetArgType: Type.vec2i },
+ 'texture_2d_array<f32>': {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ levelIsF32: true,
+ offsetArgType: Type.vec2i,
+ },
+ 'texture_3d<f32>': { coordsArgType: Type.vec3f, levelIsF32: true, offsetArgType: Type.vec3i },
+ 'texture_cube<f32>': { coordsArgType: Type.vec3f, levelIsF32: true },
+ 'texture_cube_array<f32>': {
+ coordsArgType: Type.vec3f,
+ hasArrayIndexArg: true,
+ levelIsF32: true,
+ },
+ texture_depth_2d: { coordsArgType: Type.vec2f, offsetArgType: Type.vec2i },
+ texture_depth_2d_array: {
+ coordsArgType: Type.vec2f,
+ hasArrayIndexArg: true,
+ offsetArgType: Type.vec2i,
+ },
+ texture_depth_cube: { coordsArgType: Type.vec3f },
+ texture_depth_cube_array: { coordsArgType: Type.vec3f, hasArrayIndexArg: true },
+} as const;
+
+const kTextureTypes = keysOf(kValidTextureSampleLevelParameterTypes);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplelevel')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureSampleLevelParameterTypes))
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleLevelParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, coordType, offset, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const {
+ offsetArgType,
+ coordsArgType: coordsRequiredType,
+ hasArrayIndexArg,
+ } = kValidTextureSampleLevelParameterTypes[textureType];
+
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = offset ? `, ${offsetArgType?.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleLevel(t, s, ${coordWGSL}${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = isConvertible(coordArgType, coordsRequiredType);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplelevel')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no array_index
+ .filter(t => !!kValidTextureSampleLevelParameterTypes[t.textureType].hasArrayIndexArg)
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleLevelParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, value, offset } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgType, offsetArgType } = kValidTextureSampleLevelParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleLevel(t, s, ${coordWGSL}, ${arrayWGSL}, 0${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('level_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplelevel')
+ .desc(
+ `
+Validates that only incorrect level arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('levelType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.levelType]) || t.value >= 0)
+ .expand('offset', t =>
+ kValidTextureSampleLevelParameterTypes[t.textureType].offsetArgType
+ ? [false, true]
+ : [false]
+ )
+ )
+ .fn(t => {
+ const { textureType, levelType, value, offset } = t.params;
+ const levelArgType = kValuesTypes[levelType];
+ const args = [levelArgType.create(value)];
+ const { coordsArgType, hasArrayIndexArg, offsetArgType, levelIsF32 } =
+ kValidTextureSampleLevelParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const levelWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const offsetWGSL = offset ? `, ${offsetArgType!.create(0).wgsl()}` : '';
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleLevel(t, s, ${coordWGSL}${arrayWGSL}, ${levelWGSL}${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = levelIsF32
+ ? isConvertible(levelArgType, Type.f32)
+ : isConvertible(levelArgType, Type.i32) || isConvertible(levelArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplelevel')
+ .desc(
+ `
+Validates that only incorrect offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleLevelParameterTypes[t.textureType].offsetArgType)
+ .combine('offsetType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.offsetType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, offsetType, value } = t.params;
+ const offsetArgType = kValuesTypes[offsetType];
+ const args = [offsetArgType.create(value)];
+ const {
+ coordsArgType,
+ hasArrayIndexArg,
+ offsetArgType: offsetRequiredType,
+ } = kValidTextureSampleLevelParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = args.map(arg => arg.wgsl()).join(', ');
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@fragment fn fs() -> @location(0) vec4f {
+ let v = textureSampleLevel(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(offsetArgType, offsetRequiredType!) && value >= -8 && value <= 7;
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('offset_argument,non_const')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturesamplelevel')
+ .desc(
+ `
+Validates that only non-const offset arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('varType', ['c', 'u', 'l'])
+ // filter out types with no offset
+ .filter(t => !!kValidTextureSampleLevelParameterTypes[t.textureType].offsetArgType)
+ )
+ .fn(t => {
+ const { textureType, varType } = t.params;
+ const { coordsArgType, hasArrayIndexArg, offsetArgType } =
+ kValidTextureSampleLevelParameterTypes[textureType];
+
+ const coordWGSL = coordsArgType.create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const offsetWGSL = `${offsetArgType}(${varType})`;
+
+ const code = `
+@group(0) @binding(0) var s: sampler;
+@group(0) @binding(1) var t: ${textureType};
+@group(0) @binding(2) var<uniform> u: ${offsetArgType};
+@fragment fn fs() -> @location(0) vec4f {
+ const c = 1;
+ let l = ${offsetArgType!.create(0).wgsl()};
+ let v = textureSampleLevel(t, s, ${coordWGSL}${arrayWGSL}, 0, ${offsetWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess = varType === 'c';
+ t.expectCompileResult(expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureStore.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureStore.spec.ts
new file mode 100644
index 0000000000..6613377732
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/textureStore.spec.ts
@@ -0,0 +1,168 @@
+const builtin = 'textureStore';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+
+* test textureStore coords parameter must be correct type
+* test textureStore array_index parameter must be correct type
+* test textureStore value parameter must be correct type
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import { kAllTextureFormats, kTextureFormatInfo } from '../../../../../format_info.js';
+import {
+ Type,
+ kAllScalarsAndVectors,
+ isConvertible,
+ ScalarType,
+ VectorType,
+ isUnsignedType,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kTextureColorTypeToType = {
+ sint: Type.vec4i,
+ uint: Type.vec4u,
+ float: Type.vec4f,
+ 'unfilterable-float': Type.vec4f,
+};
+
+type TextureStoreArguments = {
+ coordsArgTypes: readonly [ScalarType | VectorType, ScalarType | VectorType];
+ hasArrayIndexArg?: boolean;
+};
+
+const kValidTextureStoreParameterTypes: { [n: string]: TextureStoreArguments } = {
+ texture_storage_1d: { coordsArgTypes: [Type.i32, Type.u32] },
+ texture_storage_2d: { coordsArgTypes: [Type.vec2i, Type.vec2u] },
+ texture_storage_2d_array: {
+ coordsArgTypes: [Type.vec2i, Type.vec2u],
+ hasArrayIndexArg: true,
+ },
+ texture_storage_3d: { coordsArgTypes: [Type.vec3i, Type.vec3u] },
+} as const;
+
+const kTextureTypes = keysOf(kValidTextureStoreParameterTypes);
+const kValuesTypes = objectsToRecord(kAllScalarsAndVectors);
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('coords_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturestore')
+ .desc(
+ `
+Validates that only incorrect coords arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', keysOf(kValidTextureStoreParameterTypes))
+ .combine('coordType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-1, 0, 1] as const)
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.coordType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, coordType, value } = t.params;
+ const coordArgType = kValuesTypes[coordType];
+ const { coordsArgTypes, hasArrayIndexArg } = kValidTextureStoreParameterTypes[textureType];
+
+ const coordWGSL = coordArgType.create(value).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const format = 'rgba8unorm';
+ const valueWGSL = 'vec4f(0)';
+
+ const code = `
+@group(0) @binding(0) var t: ${textureType}<${format},write>;
+@fragment fn fs() -> @location(0) vec4f {
+ textureStore(t, ${coordWGSL}${arrayWGSL}, ${valueWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(coordArgType, coordsArgTypes[0]) ||
+ isConvertible(coordArgType, coordsArgTypes[1]);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('array_index_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturestore')
+ .desc(
+ `
+Validates that only incorrect array_index arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ // filter out types with no array_index
+ .filter(t => !!kValidTextureStoreParameterTypes[t.textureType].hasArrayIndexArg)
+ .combine('arrayIndexType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('value', [-9, -8, 0, 7, 8])
+ // filter out unsigned types with negative values
+ .filter(t => !isUnsignedType(kValuesTypes[t.arrayIndexType]) || t.value >= 0)
+ )
+ .fn(t => {
+ const { textureType, arrayIndexType, value } = t.params;
+ const arrayIndexArgType = kValuesTypes[arrayIndexType];
+ const args = [arrayIndexArgType.create(value)];
+ const { coordsArgTypes } = kValidTextureStoreParameterTypes[textureType];
+
+ const coordWGSL = coordsArgTypes[0].create(0).wgsl();
+ const arrayWGSL = args.map(arg => arg.wgsl()).join(', ');
+ const format = 'rgba8unorm';
+ const valueWGSL = 'vec4f(0)';
+
+ const code = `
+@group(0) @binding(0) var t: ${textureType}<${format}, write>;
+@fragment fn fs() -> @location(0) vec4f {
+ textureStore(t, ${coordWGSL}, ${arrayWGSL}, ${valueWGSL});
+ return vec4f(0);
+}
+`;
+ const expectSuccess =
+ isConvertible(arrayIndexArgType, Type.i32) || isConvertible(arrayIndexArgType, Type.u32);
+ t.expectCompileResult(expectSuccess, code);
+ });
+
+g.test('value_argument')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#texturestore')
+ .desc(
+ `
+Validates that only incorrect value arguments are rejected by ${builtin}
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', kTextureTypes)
+ .combine('valueType', keysOf(kValuesTypes))
+ .beginSubcases()
+ .combine('format', kAllTextureFormats)
+ // filter to only storage texture formats.
+ .filter(t => !!kTextureFormatInfo[t.format].color?.storage)
+ .combine('value', [0, 1, 2])
+ )
+ .fn(t => {
+ const { textureType, valueType, format, value } = t.params;
+ const valueArgType = kValuesTypes[valueType];
+ const args = [valueArgType.create(value)];
+ const { coordsArgTypes, hasArrayIndexArg } = kValidTextureStoreParameterTypes[textureType];
+
+ const coordWGSL = coordsArgTypes[0].create(0).wgsl();
+ const arrayWGSL = hasArrayIndexArg ? ', 0' : '';
+ const valueWGSL = args.map(arg => arg.wgsl()).join(', ');
+
+ const code = `
+@group(0) @binding(0) var t: ${textureType}<${format}, write>;
+@fragment fn fs() -> @location(0) vec4f {
+ textureStore(t, ${coordWGSL}${arrayWGSL}, ${valueWGSL});
+ return vec4f(0);
+}
+`;
+ const colorType = kTextureFormatInfo[format].color?.type;
+ const requiredValueType = kTextureColorTypeToType[colorType!];
+ const expectSuccess = isConvertible(valueArgType, requiredValueType);
+ t.expectCompileResult(expectSuccess, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/trunc.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/trunc.spec.ts
new file mode 100644
index 0000000000..e0bed7dc5e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/trunc.spec.ts
@@ -0,0 +1,94 @@
+const builtin = 'trunc';
+export const description = `
+Validation tests for the ${builtin}() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../../common/util/data_tables.js';
+import {
+ Type,
+ kConvertableToFloatScalarsAndVectors,
+ scalarTypeOf,
+} from '../../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+import {
+ fullRangeForType,
+ kConstantAndOverrideStages,
+ stageSupportsType,
+ validateConstOrOverrideBuiltinEval,
+} from './const_override_validation.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidArgumentTypes = objectsToRecord(kConvertableToFloatScalarsAndVectors);
+
+g.test('values')
+ .desc(
+ `
+Validates that constant evaluation and override evaluation of ${builtin}() error on invalid inputs.
+`
+ )
+ .params(u =>
+ u
+ .combine('stage', kConstantAndOverrideStages)
+ .combine('type', keysOf(kValidArgumentTypes))
+ .filter(u => stageSupportsType(u.stage, kValidArgumentTypes[u.type]))
+ .beginSubcases()
+ .expand('value', u => fullRangeForType(kValidArgumentTypes[u.type]))
+ )
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kValidArgumentTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const expectedResult = true;
+
+ const type = kValidArgumentTypes[t.params.type];
+ validateConstOrOverrideBuiltinEval(
+ t,
+ builtin,
+ expectedResult,
+ [type.create(t.params.value)],
+ t.params.stage
+ );
+ });
+
+const kArgCases = {
+ good: '(1.2)',
+ bad_no_parens: '',
+ // Bad number of args
+ bad_0args: '()',
+ bad_2arg: '(1.2, 2.3)',
+ // Bad value for arg 0
+ bad_0bool: '(false)',
+ bad_0array: '(array(1.1,2.2))',
+ bad_0struct: '(modf(2.2))',
+ bad_0uint: '(1u)',
+ bad_0int: '(1i)',
+ bad_0vec2i: '(vec2i())',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3i: '(vec3i())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4i: '(vec4i())',
+ bad_0vec4u: '(vec4u())',
+};
+
+g.test('args')
+ .desc(`Test compilation failure of ${builtin} with variously shaped and typed arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.arg === 'good',
+ `const c = ${builtin}${kArgCases[t.params.arg]};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${builtin} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${builtin}${kArgCases['good']}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16float.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16float.spec.ts
new file mode 100644
index 0000000000..bac245f99a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16float.spec.ts
@@ -0,0 +1,62 @@
+const kFn = 'unpack2x16float';
+export const description = `Validate ${kFn}`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kArgCases = {
+ good_u32: '(1u)',
+ good_aint: '(1)',
+ bad_0args: '()',
+ bad_2args: '(1u,2u)',
+ bad_i32: '(1i)',
+ bad_f32: '(1f)',
+ bad_f16: '(1h)',
+ bad_bool: '(false)',
+ bad_vec2u: '(vec2u())',
+ bad_vec3u: '(vec3u())',
+ bad_vec4u: '(vec4u())',
+ bad_array: '(array(1))',
+ bad_struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good_u32'];
+const kReturnType = 'vec2f';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.arg === 'bad_f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let code = '';
+ if (t.params.arg === 'bad_f16') {
+ code += 'enable f16;\n';
+ }
+ code += `const c = ${kFn}${kArgCases[t.params.arg]};`;
+
+ t.expectCompileResult(t.params.arg.startsWith('good'), code);
+ });
+
+g.test('return')
+ .desc(`Test ${kFn} return value type ${kReturnType}`)
+ .params(u => u.combine('type', ['vec2u', 'vec2i', 'vec2f', 'vec2h', 'vec4f', 'vec3f', 'f32']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.type === kReturnType,
+ `const c: ${t.params.type} = ${kFn}${kGoodArgs};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16snorm.spec.ts
new file mode 100644
index 0000000000..d1cd6c5c4d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16snorm.spec.ts
@@ -0,0 +1,62 @@
+const kFn = 'unpack2x16snorm';
+export const description = `Validate ${kFn}`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kArgCases = {
+ good_u32: '(1u)',
+ good_aint: '(1)',
+ bad_0args: '()',
+ bad_2args: '(1u,2u)',
+ bad_i32: '(1i)',
+ bad_f32: '(1f)',
+ bad_f16: '(1h)',
+ bad_bool: '(false)',
+ bad_vec2u: '(vec2u())',
+ bad_vec3u: '(vec3u())',
+ bad_vec4u: '(vec4u())',
+ bad_array: '(array(1))',
+ bad_struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good_u32'];
+const kReturnType = 'vec2f';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.arg === 'bad_f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let code = '';
+ if (t.params.arg === 'bad_f16') {
+ code += 'enable f16;\n';
+ }
+ code += `const c = ${kFn}${kArgCases[t.params.arg]};`;
+
+ t.expectCompileResult(t.params.arg.startsWith('good'), code);
+ });
+
+g.test('return')
+ .desc(`Test ${kFn} return value type ${kReturnType}`)
+ .params(u => u.combine('type', ['vec2u', 'vec2i', 'vec2f', 'vec2h', 'vec4f', 'vec3f', 'f32']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.type === kReturnType,
+ `const c: ${t.params.type} = ${kFn}${kGoodArgs};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16unorm.spec.ts
new file mode 100644
index 0000000000..7fcc96f22f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack2x16unorm.spec.ts
@@ -0,0 +1,62 @@
+const kFn = 'unpack2x16unorm';
+export const description = `Validate ${kFn}`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kArgCases = {
+ good_u32: '(1u)',
+ good_aint: '(1)',
+ bad_0args: '()',
+ bad_2args: '(1u,2u)',
+ bad_i32: '(1i)',
+ bad_f32: '(1f)',
+ bad_f16: '(1h)',
+ bad_bool: '(false)',
+ bad_vec2u: '(vec2u())',
+ bad_vec3u: '(vec3u())',
+ bad_vec4u: '(vec4u())',
+ bad_array: '(array(1))',
+ bad_struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good_u32'];
+const kReturnType = 'vec2f';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.arg === 'bad_f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let code = '';
+ if (t.params.arg === 'bad_f16') {
+ code += 'enable f16;\n';
+ }
+ code += `const c = ${kFn}${kArgCases[t.params.arg]};`;
+
+ t.expectCompileResult(t.params.arg.startsWith('good'), code);
+ });
+
+g.test('return')
+ .desc(`Test ${kFn} return value type ${kReturnType}`)
+ .params(u => u.combine('type', ['vec2u', 'vec2i', 'vec2f', 'vec2h', 'vec4f', 'vec3f', 'f32']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.type === kReturnType,
+ `const c: ${t.params.type} = ${kFn}${kGoodArgs};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4x8snorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4x8snorm.spec.ts
new file mode 100644
index 0000000000..98052f4494
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4x8snorm.spec.ts
@@ -0,0 +1,62 @@
+const kFn = 'unpack4x8snorm';
+export const description = `Validate ${kFn}`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kArgCases = {
+ good_u32: '(1u)',
+ good_aint: '(1)',
+ bad_0args: '()',
+ bad_2args: '(1u,2u)',
+ bad_i32: '(1i)',
+ bad_f32: '(1f)',
+ bad_f16: '(1h)',
+ bad_bool: '(false)',
+ bad_vec2u: '(vec2u())',
+ bad_vec3u: '(vec3u())',
+ bad_vec4u: '(vec4u())',
+ bad_array: '(array(1))',
+ bad_struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good_u32'];
+const kReturnType = 'vec4f';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.arg === 'bad_f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let code = '';
+ if (t.params.arg === 'bad_f16') {
+ code += 'enable f16;\n';
+ }
+ code += `const c = ${kFn}${kArgCases[t.params.arg]};`;
+
+ t.expectCompileResult(t.params.arg.startsWith('good'), code);
+ });
+
+g.test('return')
+ .desc(`Test ${kFn} return value type ${kReturnType}`)
+ .params(u => u.combine('type', ['vec4u', 'vec4i', 'vec4f', 'vec4h', 'vec3f', 'vec2f', 'f32']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.type === kReturnType,
+ `const c: ${t.params.type} = ${kFn}${kGoodArgs};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4x8unorm.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4x8unorm.spec.ts
new file mode 100644
index 0000000000..746443641a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4x8unorm.spec.ts
@@ -0,0 +1,62 @@
+const kFn = 'unpack4x8unorm';
+export const description = `Validate ${kFn}`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kArgCases = {
+ good_u32: '(1u)',
+ good_aint: '(1)',
+ bad_0args: '()',
+ bad_2args: '(1u,2u)',
+ bad_i32: '(1i)',
+ bad_f32: '(1f)',
+ bad_f16: '(1h)',
+ bad_bool: '(false)',
+ bad_vec2u: '(vec2u())',
+ bad_vec3u: '(vec3u())',
+ bad_vec4u: '(vec4u())',
+ bad_array: '(array(1))',
+ bad_struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good_u32'];
+const kReturnType = 'vec4f';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.arg === 'bad_f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ let code = '';
+ if (t.params.arg === 'bad_f16') {
+ code += 'enable f16;\n';
+ }
+ code += `const c = ${kFn}${kArgCases[t.params.arg]};`;
+
+ t.expectCompileResult(t.params.arg.startsWith('good'), code);
+ });
+
+g.test('return')
+ .desc(`Test ${kFn} return value type ${kReturnType}`)
+ .params(u => u.combine('type', ['vec4u', 'vec4i', 'vec4f', 'vec4h', 'vec3f', 'vec2f', 'f32']))
+ .fn(t => {
+ t.expectCompileResult(
+ t.params.type === kReturnType,
+ `const c: ${t.params.type} = ${kFn}${kGoodArgs};`
+ );
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4xI8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4xI8.spec.ts
new file mode 100644
index 0000000000..9736818b71
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4xI8.spec.ts
@@ -0,0 +1,61 @@
+export const description = `Validate unpack4xI8`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kFeature = 'packed_4x8_integer_dot_product';
+const kFn = 'unpack4xI8';
+const kArgCases = {
+ good: '(1u)',
+ bad_0args: '()',
+ bad_2args: '(1u,2u)',
+ bad_0i32: '(1i)',
+ bad_0f32: '(1f)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4u: '(vec4u())',
+ bad_0array: '(array(1))',
+ bad_0struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('unsupported')
+ .desc(`Test absence of ${kFn} when ${kFeature} is not supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(false, code);
+ });
+
+g.test('supported')
+ .desc(`Test presence of ${kFn} when ${kFeature} is supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(true, code);
+ });
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ t.expectCompileResult(t.params.arg === 'good', `const c = ${kFn}${kArgCases[t.params.arg]};`);
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4xU8.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4xU8.spec.ts
new file mode 100644
index 0000000000..34b96a4615
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/unpack4xU8.spec.ts
@@ -0,0 +1,61 @@
+export const description = `Validate unpack4xU8`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+const kFeature = 'packed_4x8_integer_dot_product';
+const kFn = 'unpack4xU8';
+const kArgCases = {
+ good: '(1u)',
+ bad_0args: '()',
+ bad_2args: '(1u,2u)',
+ bad_0i32: '(1i)',
+ bad_0f32: '(1f)',
+ bad_0bool: '(false)',
+ bad_0vec2u: '(vec2u())',
+ bad_0vec3u: '(vec3u())',
+ bad_0vec4u: '(vec4u())',
+ bad_0array: '(array(1))',
+ bad_0struct: '(modf(1.1))',
+};
+const kGoodArgs = kArgCases['good'];
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('unsupported')
+ .desc(`Test absence of ${kFn} when ${kFeature} is not supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(false, code);
+ });
+
+g.test('supported')
+ .desc(`Test presence of ${kFn} when ${kFeature} is supported.`)
+ .params(u => u.combine('requires', [false, true]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const preamble = t.params.requires ? `requires ${kFeature}; ` : '';
+ const code = `${preamble}const c = ${kFn}${kGoodArgs};`;
+ t.expectCompileResult(true, code);
+ });
+
+g.test('args')
+ .desc(`Test compilation failure of ${kFn} with various numbers of and types of arguments`)
+ .params(u => u.combine('arg', keysOf(kArgCases)))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ t.expectCompileResult(t.params.arg === 'good', `const c = ${kFn}${kArgCases[t.params.arg]};`);
+ });
+
+g.test('must_use')
+ .desc(`Result of ${kFn} must be used`)
+ .params(u => u.combine('use', [true, false]))
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported(kFeature);
+ const use_it = t.params.use ? '_ = ' : '';
+ t.expectCompileResult(t.params.use, `fn f() { ${use_it}${kFn}${kGoodArgs}; }`);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/workgroupUniformLoad.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/workgroupUniformLoad.spec.ts
new file mode 100644
index 0000000000..ac7fd042eb
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/call/builtin/workgroupUniformLoad.spec.ts
@@ -0,0 +1,122 @@
+export const description = `
+Validation tests for the workgroupUniformLoad() builtin.
+`;
+
+import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kEntryPoints = {
+ none: { supportsBarrier: true, code: `` },
+ compute: {
+ supportsBarrier: true,
+ code: `@compute @workgroup_size(1)
+fn main() {
+ foo();
+}`,
+ },
+ vertex: {
+ supportsBarrier: false,
+ code: `@vertex
+fn main() -> @builtin(position) vec4f {
+ foo();
+ return vec4f();
+}`,
+ },
+ fragment: {
+ supportsBarrier: false,
+ code: `@fragment
+fn main() {
+ foo();
+}`,
+ },
+ compute_and_fragment: {
+ supportsBarrier: false,
+ code: `@compute @workgroup_size(1)
+fn main1() {
+ foo();
+}
+
+@fragment
+fn main2() {
+ foo();
+}
+`,
+ },
+ fragment_without_call: {
+ supportsBarrier: true,
+ code: `@fragment
+fn main() {
+}
+`,
+ },
+};
+
+g.test('only_in_compute')
+ .specURL('https://www.w3.org/TR/WGSL/#sync-builtin-functions')
+ .desc(
+ `
+Synchronization functions must only be used in the compute shader stage.
+`
+ )
+ .params(u =>
+ u
+ .combine('entry_point', keysOf(kEntryPoints))
+ .combine('call', ['bar()', 'workgroupUniformLoad(&wgvar)'])
+ )
+ .fn(t => {
+ const config = kEntryPoints[t.params.entry_point];
+ const code = `
+${config.code}
+
+var<workgroup> wgvar : u32;
+
+fn bar() -> u32 {
+ return 0;
+}
+
+fn foo() {
+ _ = ${t.params.call};
+}`;
+ t.expectCompileResult(t.params.call === 'bar()' || config.supportsBarrier, code);
+ });
+
+// A list of types that contains atomics, with a single control case.
+const kAtomicTypes: string[] = [
+ 'bool', // control case
+ 'atomic<i32>',
+ 'atomic<u32>',
+ 'array<atomic<i32>, 4>',
+ 'AtomicStruct',
+];
+
+g.test('no_atomics')
+ .desc(
+ `
+The argument passed to workgroupUniformLoad cannot contain any atomic types.
+
+NOTE: Various other valid types are tested via execution tests, so we only check for invalid types here.
+`
+ )
+ .params(u =>
+ u.combine('type', kAtomicTypes).combine('call', ['bar()', 'workgroupUniformLoad(&wgvar)'])
+ )
+ .fn(t => {
+ const code = `
+struct AtomicStruct {
+ a : atomic<u32>
+}
+
+var<workgroup> wgvar : ${t.params.type};
+
+fn bar() -> bool {
+ return true;
+}
+
+fn foo() {
+ _ = ${t.params.call};
+}`;
+ t.expectCompileResult(t.params.type === 'bool' || t.params.call === 'bar()', code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/overload_resolution.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/overload_resolution.spec.ts
new file mode 100644
index 0000000000..65ba9b0f35
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/overload_resolution.spec.ts
@@ -0,0 +1,268 @@
+export const description = `Validation tests for implicit conversions and overload resolution`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../common/util/data_tables.js';
+import {
+ kAllNumericScalarsAndVectors,
+ isConvertible,
+ VectorType,
+} from '../../../util/conversion.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+interface Case {
+ expr: string;
+ valid: boolean;
+ f16?: boolean;
+}
+
+const kImplicitConversionCases: Record<string, Case> = {
+ absint_to_bool: {
+ expr: `any(1)`,
+ valid: false,
+ },
+ absint_to_u32: {
+ expr: `1 == 1u`,
+ valid: true,
+ },
+ absint_to_i32: {
+ expr: `1 == 1i`,
+ valid: true,
+ },
+ absint_to_f32: {
+ expr: `1 == 1f`,
+ valid: true,
+ },
+ absint_to_f16: {
+ expr: `1 == 1h`,
+ valid: true,
+ f16: true,
+ },
+ absfloat_to_bool: {
+ expr: `any(1.0)`,
+ valid: false,
+ },
+ absfloat_to_u32: {
+ expr: `1.0 == 1u`,
+ valid: false,
+ },
+ absfloat_to_i32: {
+ expr: `1.0 == 1i`,
+ valid: false,
+ },
+ absfloat_to_f32: {
+ expr: `1.0 == 1f`,
+ valid: true,
+ },
+ absfloat_to_f16: {
+ expr: `1.0 == 1h`,
+ valid: true,
+ f16: true,
+ },
+ vector_absint_to_bool: {
+ expr: `any(vec2(1))`,
+ valid: false,
+ },
+ vector_absint_to_u32: {
+ expr: `all(vec2(1) == vec2u(1u))`,
+ valid: true,
+ },
+ vector_absint_to_i32: {
+ expr: `all(vec3(1) == vec3i(1i))`,
+ valid: true,
+ },
+ vector_absint_to_f32: {
+ expr: `all(vec4(1) == vec4f(1f))`,
+ valid: true,
+ },
+ vector_absint_to_f16: {
+ expr: `all(vec2(1) == vec2h(1h))`,
+ valid: true,
+ f16: true,
+ },
+ vector_absfloat_to_bool: {
+ expr: `any(vec2(1.0))`,
+ valid: false,
+ },
+ vector_absfloat_to_u32: {
+ expr: `all(vec2(1.0) == vec2u(1u))`,
+ valid: false,
+ },
+ vector_absfloat_to_i32: {
+ expr: `all(vec3(1.0) == vec2i(1i))`,
+ valid: false,
+ },
+ vector_absfloat_to_f32: {
+ expr: `all(vec4(1.0) == vec4f(1f))`,
+ valid: true,
+ },
+ vector_absfloat_to_f16: {
+ expr: `all(vec2(1.0) == vec2h(1h))`,
+ valid: true,
+ f16: true,
+ },
+ vector_swizzle_integer: {
+ expr: `vec2(1).x == 1i`,
+ valid: true,
+ },
+ vector_swizzle_float: {
+ expr: `vec2(1).y == 1f`,
+ valid: true,
+ },
+ vector_default_ctor_integer: {
+ expr: `all(vec3().xy == vec2i())`,
+ valid: true,
+ },
+ vector_default_ctor_abstract: {
+ expr: `all(vec3().xy == vec2())`,
+ valid: true,
+ },
+ vector_swizzle_abstract: {
+ expr: `vec4(1f).x == 1`,
+ valid: true,
+ },
+ vector_abstract_to_integer: {
+ expr: `all(vec4(1) == vec4i(1))`,
+ valid: true,
+ },
+ vector_wrong_result_i32: {
+ expr: `vec2(1,2f).x == 1i`,
+ valid: false,
+ },
+ vector_wrong_result_f32: {
+ expr: `vec2(1,2i).y == 2f`,
+ valid: false,
+ },
+ vector_wrong_result_splat: {
+ expr: `vec2(1.0).x == 1i`,
+ valid: false,
+ },
+ array_absint_to_bool: {
+ expr: `any(array(1)[0])`,
+ valid: false,
+ },
+ array_absint_to_u32: {
+ expr: `array(1)[0] == array<u32,1>(1u)[0]`,
+ valid: true,
+ },
+ array_absint_to_i32: {
+ expr: `array(1)[0] == array<i32,1>(1i)[0]`,
+ valid: true,
+ },
+ array_absint_to_f32: {
+ expr: `array(1)[0] == array<f32,1>(1f)[0]`,
+ valid: true,
+ },
+ array_absint_to_f16: {
+ expr: `array(1)[0] == array<f16,1>(1h)[0]`,
+ valid: true,
+ f16: true,
+ },
+ array_absfloat_to_bool: {
+ expr: `any(array(1.0)[0])`,
+ valid: false,
+ },
+ array_absfloat_to_u32: {
+ expr: `array(1.0)[0] == array<u32,1>(1u)[0]`,
+ valid: false,
+ },
+ array_absfloat_to_i32: {
+ expr: `array(1.0)[0] == array<i32,1>(1i)[0]`,
+ valid: false,
+ },
+ array_absfloat_to_f32: {
+ expr: `array(1.0)[0] == array<f32,1>(1f)[0]`,
+ valid: true,
+ },
+ array_absfloat_to_f16: {
+ expr: `array(1.0)[0] == array<f16,1>(1h)[0]`,
+ valid: true,
+ f16: true,
+ },
+ mat2x2_index_absint: {
+ expr: `all(mat2x2(1,2,3,4)[0] == vec2(1,2))`,
+ valid: true,
+ },
+ mat2x2_index_absfloat: {
+ expr: `all(mat2x2(1,2,3,4)[1] == vec2(3.0,4.0))`,
+ valid: true,
+ },
+ mat2x2_index_float: {
+ expr: `all(mat2x2(0,0,0,0)[1] == vec2f())`,
+ valid: true,
+ },
+ mat2x2_wrong_result: {
+ expr: `all(mat2x2(0f,0,0,0)[0] == vec2h())`,
+ valid: false,
+ f16: true,
+ },
+};
+
+g.test('implicit_conversions')
+ .desc('Test implicit conversions')
+ .params(u => u.combine('case', keysOf(kImplicitConversionCases)))
+ .beforeAllSubcases(t => {
+ if (kImplicitConversionCases[t.params.case].f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const testcase = kImplicitConversionCases[t.params.case];
+ const code = `${testcase.f16 ? 'enable f16;' : ''}
+ const_assert ${testcase.expr};`;
+ t.expectCompileResult(testcase.valid, code);
+ });
+
+const kTypes = objectsToRecord(kAllNumericScalarsAndVectors);
+const kTypeKeys = keysOf(kTypes);
+
+g.test('overload_resolution')
+ .desc('Test overload resolution')
+ .params(u =>
+ u
+ .combine('arg1', kTypeKeys)
+ .combine('arg2', kTypeKeys)
+ .beginSubcases()
+ .combine('op', ['min', 'max'] as const)
+ .filter(t => {
+ if (t.arg1 === t.arg2) {
+ return false;
+ }
+ const t1 = kTypes[t.arg1];
+ const t2 = kTypes[t.arg2];
+ const t1IsVector = t1 instanceof VectorType;
+ const t2IsVector = t2 instanceof VectorType;
+ if (t1IsVector !== t2IsVector) {
+ return false;
+ }
+ if (t1IsVector && t2IsVector && t1.size !== t2.size) {
+ return false;
+ }
+ return true;
+ })
+ )
+ .beforeAllSubcases(t => {
+ const t1 = kTypes[t.params.arg1];
+ const t2 = kTypes[t.params.arg2];
+ if (t1.requiresF16() || t2.requiresF16()) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const t1 = kTypes[t.params.arg1];
+ const t2 = kTypes[t.params.arg2];
+ const resTy = isConvertible(t1, t2) ? t2 : t1;
+ const enable = `${t1.requiresF16() || t2.requiresF16() ? 'enable f16;' : ''}`;
+ const min = 50;
+ const max = 100;
+ const res = t.params.op === 'min' ? min : max;
+ const v1 = t1.create(min).wgsl();
+ const v2 = t2.create(max).wgsl();
+ const resV = resTy.create(res).wgsl();
+ const expr = `${t.params.op}(${v1}, ${v2}) == ${resV}`;
+ const assertExpr = t1 instanceof VectorType ? `all(${expr})` : expr;
+ const code = `${enable}
+ const_assert ${assertExpr};`;
+ t.expectCompileResult(isConvertible(t1, t2) || isConvertible(t2, t1), code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/precedence.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/precedence.spec.ts
new file mode 100644
index 0000000000..15f7ad4a97
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/precedence.spec.ts
@@ -0,0 +1,188 @@
+export const description = `
+Validation tests for operator precedence.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// Bit set for the binary operator groups.
+const kMultiplicative = 1 << 0;
+const kAdditive = 1 << 1;
+const kShift = 1 << 2;
+const kRelational = 1 << 3;
+const kBinaryAnd = 1 << 4;
+const kBinaryXor = 1 << 5;
+const kBinaryOr = 1 << 6;
+const kLogical = 1 << 7;
+
+// Set of other operators that each operator can precede without any parentheses.
+const kCanPrecedeWithoutParens: Record<number, number> = {};
+kCanPrecedeWithoutParens[kMultiplicative] = kMultiplicative | kAdditive | kRelational;
+kCanPrecedeWithoutParens[kAdditive] = kMultiplicative | kAdditive | kRelational;
+kCanPrecedeWithoutParens[kShift] = kRelational | kLogical;
+kCanPrecedeWithoutParens[kRelational] = kMultiplicative | kAdditive | kShift | kLogical;
+kCanPrecedeWithoutParens[kBinaryAnd] = kBinaryAnd;
+kCanPrecedeWithoutParens[kBinaryXor] = kBinaryXor;
+kCanPrecedeWithoutParens[kBinaryOr] = kBinaryOr;
+kCanPrecedeWithoutParens[kLogical] = kRelational;
+
+// The list of binary operators.
+interface BinaryOperatorInfo {
+ op: string;
+ group: number;
+}
+const kBinaryOperators: Record<string, BinaryOperatorInfo> = {
+ mul: { op: '*', group: kMultiplicative },
+ div: { op: '/', group: kMultiplicative },
+ mod: { op: '%', group: kMultiplicative },
+
+ add: { op: '+', group: kAdditive },
+ sub: { op: '-', group: kAdditive },
+
+ shl: { op: '<<', group: kShift },
+ shr: { op: '>>', group: kShift },
+
+ lt: { op: '<', group: kRelational },
+ gt: { op: '>', group: kRelational },
+ le: { op: '<=', group: kRelational },
+ ge: { op: '>=', group: kRelational },
+ eq: { op: '==', group: kRelational },
+ ne: { op: '!=', group: kRelational },
+
+ bin_and: { op: '&', group: kBinaryAnd },
+ bin_xor: { op: '^', group: kBinaryXor },
+ bin_or: { op: '|', group: kBinaryOr },
+
+ log_and: { op: '&&', group: kLogical },
+ log_or: { op: '||', group: kLogical },
+};
+
+g.test('binary_requires_parentheses')
+ .desc(
+ `
+ Validates that certain binary operators require parentheses to bind correctly.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op1', keysOf(kBinaryOperators))
+ .combine('op2', keysOf(kBinaryOperators))
+ .filter(p => {
+ // Skip expressions that would parse as template lists.
+ if (p.op1 === 'lt' && ['gt', 'ge', 'shr'].includes(p.op2)) {
+ return false;
+ }
+ // Only combine logical operators with relational operators.
+ if (kBinaryOperators[p.op1].group === kLogical) {
+ return kBinaryOperators[p.op2].group === kRelational;
+ }
+ if (kBinaryOperators[p.op2].group === kLogical) {
+ return kBinaryOperators[p.op1].group === kRelational;
+ }
+ return true;
+ })
+ )
+ .fn(t => {
+ const op1 = kBinaryOperators[t.params.op1];
+ const op2 = kBinaryOperators[t.params.op2];
+ const code = `
+var<private> a : ${op1.group === kLogical ? 'bool' : 'u32'};
+var<private> b : u32;
+var<private> c : ${op2.group === kLogical ? 'bool' : 'u32'};
+fn foo() {
+ let foo = a ${op1.op} b ${op2.op} c;
+}
+`;
+
+ const valid = (kCanPrecedeWithoutParens[op1.group] & op2.group) !== 0;
+ t.expectCompileResult(valid, code);
+ });
+
+g.test('mixed_logical_requires_parentheses')
+ .desc(
+ `
+ Validates that mixed logical operators require parentheses to bind correctly.
+ `
+ )
+ .params(u =>
+ u
+ .combine('op1', keysOf(kBinaryOperators))
+ .combine('op2', keysOf(kBinaryOperators))
+ .combine('parens', ['none', 'left', 'right'])
+ .filter(p => {
+ const group1 = kBinaryOperators[p.op1].group;
+ const group2 = kBinaryOperators[p.op2].group;
+ return group1 === kLogical && group2 === kLogical;
+ })
+ )
+ .fn(t => {
+ const op1 = kBinaryOperators[t.params.op1];
+ const op2 = kBinaryOperators[t.params.op2];
+ let expr = `a ${op1.op} b ${op2.op} c;`;
+ if (t.params.parens === 'left') {
+ expr = `(a ${op1.op} b) ${op2.op} c;`;
+ } else if (t.params.parens === 'right') {
+ expr = `a ${op1.op} (b ${op2.op} c);`;
+ }
+ const code = `
+var<private> a : bool;
+var<private> b : bool;
+var<private> c : bool;
+fn foo() {
+ let bar = ${expr};
+}
+`;
+ const valid = t.params.parens !== 'none' || t.params.op1 === t.params.op2;
+ t.expectCompileResult(valid, code);
+ });
+
+// The list of miscellaneous other test cases.
+interface Expression {
+ expr: string;
+ result: boolean;
+}
+const kExpressions: Record<string, Expression> = {
+ neg_member: { expr: '- str . a', result: true },
+ comp_member: { expr: '~ str . a', result: true },
+ addr_member: { expr: '& str . a', result: true },
+ log_and_member: { expr: 'false && str . b', result: true },
+ log_or_member: { expr: 'false || str . b', result: true },
+ and_addr: { expr: ' v & &str .a', result: false },
+ and_addr_paren: { expr: 'v & (&str).a', result: true },
+ deref_member: { expr: ' * ptr_str . a', result: false },
+ deref_member_paren: { expr: '(* ptr_str) . a', result: true },
+ deref_idx: { expr: ' * ptr_vec [0]', result: false },
+ deref_idx_paren: { expr: '(* ptr_vec) [1]', result: true },
+};
+
+g.test('other')
+ .desc(
+ `
+ Test that other operator precedence rules are correctly implemented.
+ `
+ )
+ .params(u => u.combine('expr', keysOf(kExpressions)))
+ .fn(t => {
+ const expr = kExpressions[t.params.expr];
+ const wgsl = `
+ struct S {
+ a: i32,
+ b: bool,
+ }
+
+ fn main() {
+ var v = 42;
+ var vec = vec4();
+ var str = S(42, false);
+ let ptr_vec = &vec;
+ let ptr_str = &str;
+
+ let foo = ${expr.expr};
+ }
+ `;
+
+ t.expectCompileResult(expr.result, wgsl);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/address_of_and_indirection.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/address_of_and_indirection.spec.ts
new file mode 100644
index 0000000000..7eca4286b9
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/address_of_and_indirection.spec.ts
@@ -0,0 +1,243 @@
+export const description = `
+Validation tests for unary address-of and indirection (dereference)
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kAddressSpaces = ['function', 'private', 'workgroup', 'uniform', 'storage'];
+const kAccessModes = ['read', 'read_write'];
+const kStorageTypes = ['bool', 'u32', 'i32', 'f32', 'f16'];
+const kCompositeTypes = ['array', 'struct', 'vec', 'mat'];
+const kDerefTypes = {
+ deref_address_of_identifier: {
+ wgsl: `(*(&a))`,
+ requires_pointer_composite_access: false,
+ },
+ deref_pointer: {
+ wgsl: `(*p)`,
+ requires_pointer_composite_access: false,
+ },
+ address_of_identifier: {
+ wgsl: `(&a)`,
+ requires_pointer_composite_access: true,
+ },
+ pointer: {
+ wgsl: `p`,
+ requires_pointer_composite_access: true,
+ },
+};
+
+g.test('basic')
+ .desc(
+ `Validates address-of (&) every supported variable type, ensuring the type is correct by
+ assigning to an explicitly typed pointer. Also validates dereferencing the reference,
+ ensuring the type is correct by assigning to an explicitly typed variable.`
+ )
+ .params(u =>
+ u
+ .combine('addressSpace', kAddressSpaces)
+ .combine('accessMode', kAccessModes)
+ .combine('storageType', kStorageTypes)
+ .combine('derefType', keysOf(kDerefTypes))
+ .filter(t => {
+ if (t.storageType === 'bool') {
+ return t.addressSpace === 'function' || t.addressSpace === 'private';
+ }
+ return true;
+ })
+ .filter(t => {
+ // This test does not test composite access
+ return !kDerefTypes[t.derefType].requires_pointer_composite_access;
+ })
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.storageType === 'f16') {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ }
+ })
+ .fn(t => {
+ const isLocal = t.params.addressSpace === 'function';
+ const deref = kDerefTypes[t.params.derefType];
+ // Only specify access mode for storage buffers
+ const commaAccessMode = t.params.addressSpace === 'storage' ? `, ${t.params.accessMode}` : '';
+
+ let varDecl = '';
+ if (t.params.addressSpace === 'uniform' || t.params.addressSpace === 'storage') {
+ varDecl += '@group(0) @binding(0) ';
+ }
+ varDecl += `var<${t.params.addressSpace}${commaAccessMode}> a : VarType;`;
+
+ const wgsl = `
+ ${t.params.storageType === 'f16' ? 'enable f16;' : ''}
+
+ alias VarType = ${t.params.storageType};
+ alias PtrType = ptr<${t.params.addressSpace}, VarType ${commaAccessMode}>;
+
+ ${isLocal ? '' : varDecl}
+
+ fn foo() {
+ ${isLocal ? varDecl : ''}
+ let p : PtrType = &a;
+ var deref : VarType = ${deref.wgsl};
+ }
+ `;
+
+ t.expectCompileResult(true, wgsl);
+ });
+
+g.test('composite')
+ .desc(
+ `Validates address-of (&) every supported variable type for composite types, ensuring the type
+ is correct by assigning to an explicitly typed pointer. Also validates dereferencing the
+ reference followed by member/index access, ensuring the type is correct by assigning to an
+ explicitly typed variable.`
+ )
+ .params(u =>
+ u
+ .combine('addressSpace', kAddressSpaces)
+ .combine('compositeType', kCompositeTypes)
+ .combine('storageType', kStorageTypes)
+ .beginSubcases()
+ .combine('derefType', keysOf(kDerefTypes))
+ .combine('accessMode', kAccessModes)
+ .filter(t => {
+ if (t.storageType === 'bool') {
+ return t.addressSpace === 'function' || t.addressSpace === 'private';
+ }
+ return true;
+ })
+ .filter(t => {
+ if (t.compositeType === 'mat') {
+ return t.storageType === 'f32' || t.storageType === 'f16';
+ }
+ return true;
+ })
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.storageType === 'f16') {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ }
+ })
+ .fn(t => {
+ const isLocal = t.params.addressSpace === 'function';
+ const deref = kDerefTypes[t.params.derefType];
+ // Only specify access mode for storage buffers
+ const commaAccessMode = t.params.addressSpace === 'storage' ? `, ${t.params.accessMode}` : '';
+
+ let varDecl = '';
+ if (t.params.addressSpace === 'uniform' || t.params.addressSpace === 'storage') {
+ varDecl += '@group(0) @binding(0) ';
+ }
+ varDecl += `var<${t.params.addressSpace}${commaAccessMode}> a : VarType;`;
+
+ let wgsl = `
+ ${t.params.storageType === 'f16' ? 'enable f16;' : ''}`;
+
+ switch (t.params.compositeType) {
+ case 'array':
+ wgsl += `
+ struct S { @align(16) member : ${t.params.storageType} }
+ alias VarType = array<S, 10>;
+ alias PtrType = ptr<${t.params.addressSpace}, VarType ${commaAccessMode}>;
+ ${isLocal ? '' : varDecl}
+
+ fn foo() {
+ ${isLocal ? varDecl : ''}
+ let p : PtrType = &a;
+ var deref : ${t.params.storageType} = ${deref.wgsl}[0].member;
+ }`;
+ break;
+ case 'struct':
+ wgsl += `
+ struct S { member : ${t.params.storageType} }
+ alias VarType = S;
+ alias PtrType = ptr<${t.params.addressSpace}, VarType ${commaAccessMode}>;
+ ${isLocal ? '' : varDecl}
+
+ fn foo() {
+ ${isLocal ? varDecl : ''}
+ let p : PtrType = &a;
+ var deref : ${t.params.storageType} = ${deref.wgsl}.member;
+ }`;
+ break;
+ case 'vec':
+ wgsl += `
+ alias VarType = vec3<${t.params.storageType}>;
+ alias PtrType = ptr<${t.params.addressSpace}, VarType ${commaAccessMode}>;
+ ${isLocal ? '' : varDecl}
+
+ fn foo() {
+ ${isLocal ? varDecl : ''}
+ let p : PtrType = &a;
+ var deref_member : ${t.params.storageType} = ${deref.wgsl}.x;
+ var deref_index : ${t.params.storageType} = ${deref.wgsl}[0];
+ }`;
+ break;
+ case 'mat':
+ wgsl += `
+ alias VarType = mat2x3<${t.params.storageType}>;
+ alias PtrType = ptr<${t.params.addressSpace}, VarType ${commaAccessMode}>;
+ ${isLocal ? '' : varDecl}
+
+ fn foo() {
+ ${isLocal ? varDecl : ''}
+ let p : PtrType = &a;
+ var deref_vec : vec3<${t.params.storageType}> = ${deref.wgsl}[0];
+ var deref_elem : ${t.params.storageType} = ${deref.wgsl}[0][0];
+ }`;
+ break;
+ }
+
+ let shouldPass = true;
+ if (
+ kDerefTypes[t.params.derefType].requires_pointer_composite_access &&
+ !t.hasLanguageFeature('pointer_composite_access')
+ ) {
+ shouldPass = false;
+ }
+
+ t.expectCompileResult(shouldPass, wgsl);
+ });
+
+const kInvalidCases = {
+ address_of_let: `
+ let a = 1;
+ let p = &a;`,
+ address_of_texture: `
+ let p = &t;`,
+ address_of_sampler: `
+ let p = &s;`,
+ address_of_function: `
+ let p = &func;`,
+ address_of_vector_elem_via_member: `
+ var a : vec3<f32>();
+ let p = &a.x;`,
+ address_of_vector_elem_via_index: `
+ var a : vec3<f32>();
+ let p = &a[0];`,
+ address_of_matrix_elem: `
+ var a : mat2x3<f32>();
+ let p = &a[0][0];`,
+ deref_non_pointer: `
+ var a = 1;
+ let p = *a;
+ `,
+};
+g.test('invalid')
+ .desc('Test invalid cases of unary address-of and dereference')
+ .params(u => u.combine('case', keysOf(kInvalidCases)))
+ .fn(t => {
+ const wgsl = `
+ @group(0) @binding(0) var s : sampler;
+ @group(0) @binding(1) var t : texture_2d<f32>;
+ fn func() {}
+ fn main() {
+ ${kInvalidCases[t.params.case]}
+ }
+ `;
+ t.expectCompileResult(false, wgsl);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/arithmetic_negation.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/arithmetic_negation.spec.ts
new file mode 100644
index 0000000000..47a1a04990
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/arithmetic_negation.spec.ts
@@ -0,0 +1,114 @@
+export const description = `
+Validation tests for arithmetic negation expressions.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../common/util/data_tables.js';
+import { kAllScalarsAndVectors, scalarTypeOf, Type } from '../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// A list of scalar and vector types.
+const kScalarAndVectorTypes = objectsToRecord(kAllScalarsAndVectors);
+
+g.test('scalar_vector')
+ .desc(
+ `
+ Validates that scalar and vector numeric negation expressions are accepted for numerical types that are signed.
+ `
+ )
+ .params(u => u.combine('type', keysOf(kScalarAndVectorTypes)).beginSubcases())
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kScalarAndVectorTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kScalarAndVectorTypes[t.params.type];
+ const elementTy = scalarTypeOf(type);
+ const hasF16 = elementTy === Type.f16;
+ const code = `
+${hasF16 ? 'enable f16;' : ''}
+const rhs = ${type.create(0).wgsl()};
+const foo = -rhs;
+`;
+
+ t.expectCompileResult(elementTy.signed, code);
+ });
+
+interface InvalidTypeConfig {
+ // An expression that produces a value of the target type.
+ expr: string;
+ // A function that converts an expression of the target type into a valid negation operand.
+ control: (x: string) => string;
+}
+const kInvalidTypes: Record<string, InvalidTypeConfig> = {
+ mat2x2f: {
+ expr: 'm',
+ control: e => `${e}[0][0]`,
+ },
+
+ array: {
+ expr: 'arr',
+ control: e => `${e}[0]`,
+ },
+
+ ptr: {
+ expr: '(&b)',
+ control: e => `*${e}`,
+ },
+
+ atomic: {
+ expr: 'a',
+ control: e => `atomicLoad(&${e})`,
+ },
+
+ texture: {
+ expr: 't',
+ control: e => `textureLoad(${e}, vec2(), 0).x`,
+ },
+
+ sampler: {
+ expr: 's',
+ control: e => `textureSampleLevel(t, ${e}, vec2(), 0).x`,
+ },
+
+ struct: {
+ expr: 'str',
+ control: e => `${e}.b`,
+ },
+};
+
+g.test('invalid_types')
+ .desc(
+ `
+ Validates that arithmetic negation expressions are never accepted for non-scalar and non-vector types.
+ `
+ )
+ .params(u =>
+ u.combine('type', keysOf(kInvalidTypes)).combine('control', [true, false]).beginSubcases()
+ )
+ .fn(t => {
+ const type = kInvalidTypes[t.params.type];
+ const expr = t.params.control ? type.control(type.expr) : type.expr;
+ const code = `
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+@group(0) @binding(2) var<storage, read_write> a : atomic<i32>;
+
+struct S { b : i32 }
+
+var<private> b : i32;
+var<private> m : mat2x2f;
+var<private> arr : array<i32, 4>;
+var<private> str : S;
+
+@compute @workgroup_size(1)
+fn main() {
+ let foo = -${expr};
+}
+`;
+
+ t.expectCompileResult(t.params.control, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/bitwise_complement.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/bitwise_complement.spec.ts
new file mode 100644
index 0000000000..b5b8556b9d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/bitwise_complement.spec.ts
@@ -0,0 +1,114 @@
+export const description = `
+Validation tests for bitwise complement expressions.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../common/util/data_tables.js';
+import { kAllScalarsAndVectors, scalarTypeOf, Type } from '../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// A list of scalar and vector types.
+const kScalarAndVectorTypes = objectsToRecord(kAllScalarsAndVectors);
+
+g.test('scalar_vector')
+ .desc(
+ `
+ Validates that scalar and vector bitwise complement expressions are only accepted for integers.
+ `
+ )
+ .params(u => u.combine('type', keysOf(kScalarAndVectorTypes)).beginSubcases())
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kScalarAndVectorTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kScalarAndVectorTypes[t.params.type];
+ const elementTy = scalarTypeOf(type);
+ const hasF16 = elementTy === Type.f16;
+ const code = `
+${hasF16 ? 'enable f16;' : ''}
+const rhs = ${type.create(0).wgsl()};
+const foo = ~rhs;
+`;
+
+ t.expectCompileResult([Type.abstractInt, Type.i32, Type.u32].includes(elementTy), code);
+ });
+
+interface InvalidTypeConfig {
+ // An expression that produces a value of the target type.
+ expr: string;
+ // A function that converts an expression of the target type into a valid complement operand.
+ control: (x: string) => string;
+}
+const kInvalidTypes: Record<string, InvalidTypeConfig> = {
+ mat2x2f: {
+ expr: 'm',
+ control: e => `i32(${e}[0][0])`,
+ },
+
+ array: {
+ expr: 'arr',
+ control: e => `${e}[0]`,
+ },
+
+ ptr: {
+ expr: '(&u)',
+ control: e => `*${e}`,
+ },
+
+ atomic: {
+ expr: 'a',
+ control: e => `atomicLoad(&${e})`,
+ },
+
+ texture: {
+ expr: 't',
+ control: e => `i32(textureLoad(${e}, vec2(), 0).x)`,
+ },
+
+ sampler: {
+ expr: 's',
+ control: e => `i32(textureSampleLevel(t, ${e}, vec2(), 0).x)`,
+ },
+
+ struct: {
+ expr: 'str',
+ control: e => `${e}.u`,
+ },
+};
+
+g.test('invalid_types')
+ .desc(
+ `
+ Validates that bitwise complement expressions are never accepted for non-scalar and non-vector types.
+ `
+ )
+ .params(u =>
+ u.combine('type', keysOf(kInvalidTypes)).combine('control', [true, false]).beginSubcases()
+ )
+ .fn(t => {
+ const type = kInvalidTypes[t.params.type];
+ const expr = t.params.control ? type.control(type.expr) : type.expr;
+ const code = `
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+@group(0) @binding(2) var<storage, read_write> a : atomic<i32>;
+
+struct S { u : u32 }
+
+var<private> u : u32;
+var<private> m : mat2x2f;
+var<private> arr : array<u32, 4>;
+var<private> str : S;
+
+@compute @workgroup_size(1)
+fn main() {
+ let foo = ~${expr};
+}
+`;
+
+ t.expectCompileResult(t.params.control, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/logical_negation.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/logical_negation.spec.ts
new file mode 100644
index 0000000000..a85516ec3f
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/expression/unary/logical_negation.spec.ts
@@ -0,0 +1,114 @@
+export const description = `
+Validation tests for logical negation expressions.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { keysOf, objectsToRecord } from '../../../../../common/util/data_tables.js';
+import { kAllScalarsAndVectors, scalarTypeOf, Type } from '../../../../util/conversion.js';
+import { ShaderValidationTest } from '../../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+// A list of scalar and vector types.
+const kScalarAndVectorTypes = objectsToRecord(kAllScalarsAndVectors);
+
+g.test('scalar_vector')
+ .desc(
+ `
+ Validates that scalar and vector logical negation expressions are only accepted for bool types.
+ `
+ )
+ .params(u => u.combine('type', keysOf(kScalarAndVectorTypes)).beginSubcases())
+ .beforeAllSubcases(t => {
+ if (scalarTypeOf(kScalarAndVectorTypes[t.params.type]) === Type.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const type = kScalarAndVectorTypes[t.params.type];
+ const elementTy = scalarTypeOf(type);
+ const hasF16 = elementTy === Type.f16;
+ const code = `
+${hasF16 ? 'enable f16;' : ''}
+const rhs = ${type.create(0).wgsl()};
+const foo = !rhs;
+`;
+
+ t.expectCompileResult(elementTy === Type.bool, code);
+ });
+
+interface InvalidTypeConfig {
+ // An expression that produces a value of the target type.
+ expr: string;
+ // A function that converts an expression of the target type into a valid negation operand.
+ control: (x: string) => string;
+}
+const kInvalidTypes: Record<string, InvalidTypeConfig> = {
+ mat2x2f: {
+ expr: 'm',
+ control: e => `bool(${e}[0][0])`,
+ },
+
+ array: {
+ expr: 'arr',
+ control: e => `${e}[0]`,
+ },
+
+ ptr: {
+ expr: '(&b)',
+ control: e => `*${e}`,
+ },
+
+ atomic: {
+ expr: 'a',
+ control: e => `bool(atomicLoad(&${e}))`,
+ },
+
+ texture: {
+ expr: 't',
+ control: e => `bool(textureLoad(${e}, vec2(), 0).x)`,
+ },
+
+ sampler: {
+ expr: 's',
+ control: e => `bool(textureSampleLevel(t, ${e}, vec2(), 0).x)`,
+ },
+
+ struct: {
+ expr: 'str',
+ control: e => `${e}.b`,
+ },
+};
+
+g.test('invalid_types')
+ .desc(
+ `
+ Validates that logical negation expressions are never accepted for non-scalar and non-vector types.
+ `
+ )
+ .params(u =>
+ u.combine('type', keysOf(kInvalidTypes)).combine('control', [true, false]).beginSubcases()
+ )
+ .fn(t => {
+ const type = kInvalidTypes[t.params.type];
+ const expr = t.params.control ? type.control(type.expr) : type.expr;
+ const code = `
+@group(0) @binding(0) var t : texture_2d<f32>;
+@group(0) @binding(1) var s : sampler;
+@group(0) @binding(2) var<storage, read_write> a : atomic<i32>;
+
+struct S { b : bool }
+
+var<private> b : bool;
+var<private> m : mat2x2f;
+var<private> arr : array<bool, 4>;
+var<private> str : S;
+
+@compute @workgroup_size(1)
+fn main() {
+ let foo = !${expr};
+}
+`;
+
+ t.expectCompileResult(t.params.control, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/extension/pointer_composite_access.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/extension/pointer_composite_access.spec.ts
new file mode 100644
index 0000000000..6940dc6469
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/extension/pointer_composite_access.spec.ts
@@ -0,0 +1,130 @@
+export const description = `
+Validation tests for pointer_composite_access extension
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+function makeSource(module: string, init_expr: string, pointer_read_expr: string) {
+ return `
+ ${module}
+ fn f() {
+ var a = ${init_expr};
+ let p = &a;
+ let r = ${pointer_read_expr};
+ }`;
+}
+
+const kCases = {
+ // Via identifier 'a'
+ array_index_access_via_identifier: {
+ module: '',
+ init_expr: 'array<i32, 3>()',
+ via_deref: '(*(&a))[0]',
+ via_pointer: '(&a)[0]',
+ },
+ vector_index_access_via_identifier: {
+ module: '',
+ init_expr: 'vec3<i32>()',
+ via_deref: '(*(&a))[0]',
+ via_pointer: '(&a)[0]',
+ },
+ vector_member_access_via_identifier: {
+ module: '',
+ init_expr: 'vec3<i32>()',
+ via_deref: '(*(&a)).x',
+ via_pointer: '(&a).x',
+ },
+ matrix_index_access_via_identifier: {
+ module: '',
+ init_expr: 'mat2x3<f32>()',
+ via_deref: '(*(&a))[0]',
+ via_pointer: '(&a)[0]',
+ },
+ struct_member_access_via_identifier: {
+ module: 'struct S { a : i32, }',
+ init_expr: 'S()',
+ via_deref: '(*(&a)).a',
+ via_pointer: '(&a).a',
+ },
+ builtin_struct_modf_via_identifier: {
+ module: '',
+ init_expr: 'modf(1.5)',
+ via_deref: 'vec2((*(&a)).fract, (*(&a)).whole)',
+ via_pointer: 'vec2((&a).fract, (&a).whole)',
+ },
+ builtin_struct_frexp_via_identifier: {
+ module: '',
+ init_expr: 'frexp(1.5)',
+ via_deref: 'vec2((*(&a)).fract, f32((*(&a)).exp))',
+ via_pointer: 'vec2((&a).fract, f32((&a).exp))',
+ },
+
+ // Via pointer 'p'
+ array_index_access_via_pointer: {
+ module: '',
+ init_expr: 'array<i32, 3>()',
+ via_deref: '(*p)[0]',
+ via_pointer: 'p[0]',
+ },
+ vector_index_access_via_pointer: {
+ module: '',
+ init_expr: 'vec3<i32>()',
+ via_deref: '(*p)[0]',
+ via_pointer: 'p[0]',
+ },
+ vector_member_access_via_pointer: {
+ module: '',
+ init_expr: 'vec3<i32>()',
+ via_deref: '(*p).x',
+ via_pointer: 'p.x',
+ },
+ matrix_index_access_via_pointer: {
+ module: '',
+ init_expr: 'mat2x3<f32>()',
+ via_deref: '(*p)[0]',
+ via_pointer: 'p[0]',
+ },
+ struct_member_access_via_pointer: {
+ module: 'struct S { a : i32, }',
+ init_expr: 'S()',
+ via_deref: '(*p).a',
+ via_pointer: 'p.a',
+ },
+ builtin_struct_modf_via_pointer: {
+ module: '',
+ init_expr: 'modf(1.5)',
+ via_deref: 'vec2((*p).fract, (*p).whole)',
+ via_pointer: 'vec2(p.fract, p.whole)',
+ },
+ builtin_struct_frexp_via_pointer: {
+ module: '',
+ init_expr: 'frexp(1.5)',
+ via_deref: 'vec2((*p).fract, f32((*p).exp))',
+ via_pointer: 'vec2(p.fract, f32(p.exp))',
+ },
+};
+
+g.test('deref')
+ .desc('Baseline test: pointer deref is always valid')
+ .params(u => u.combine('case', keysOf(kCases)))
+ .fn(t => {
+ const curr = kCases[t.params.case];
+ const source = makeSource(curr.module, curr.init_expr, curr.via_deref);
+ t.expectCompileResult(true, source);
+ });
+
+g.test('pointer')
+ .desc(
+ 'Tests that direct pointer access is valid if pointer_composite_access is supported, else it should fail'
+ )
+ .params(u => u.combine('case', keysOf(kCases)))
+ .fn(t => {
+ const curr = kCases[t.params.case];
+ const source = makeSource(curr.module, curr.init_expr, curr.via_pointer);
+ const should_pass = t.hasLanguageFeature('pointer_composite_access');
+ t.expectCompileResult(should_pass, source);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/extension/readonly_and_readwrite_storage_textures.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/extension/readonly_and_readwrite_storage_textures.spec.ts
new file mode 100644
index 0000000000..c74694d4b5
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/extension/readonly_and_readwrite_storage_textures.spec.ts
@@ -0,0 +1,48 @@
+export const description = `
+Validation tests for the readonly_and_readwrite_storage_textures language feature
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { TexelFormats } from '../../types.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kFeatureName = 'readonly_and_readwrite_storage_textures';
+
+g.test('var_decl')
+ .desc(
+ `Checks that the read and read_write access modes are only allowed with the language feature present`
+ )
+ .paramsSubcasesOnly(u =>
+ u
+ .combine('type', [
+ 'texture_storage_1d',
+ 'texture_storage_2d',
+ 'texture_storage_2d_array',
+ 'texture_storage_3d',
+ ])
+ .combine('format', TexelFormats)
+ .combine('access', ['read', 'write', 'read_write'])
+ )
+ .fn(t => {
+ const { type, format, access } = t.params;
+ const source = `@group(0) @binding(0) var t : ${type}<${format.format}, ${access}>;`;
+ const requiresFeature = access !== 'write';
+ t.expectCompileResult(t.hasLanguageFeature(kFeatureName) || !requiresFeature, source);
+ });
+
+g.test('textureBarrier')
+ .desc(
+ `Checks that the textureBarrier() builtin is only allowed with the language feature present`
+ )
+ .fn(t => {
+ t.expectCompileResult(
+ t.hasLanguageFeature(kFeatureName),
+ `
+ @workgroup_size(1) @compute fn main() {
+ textureBarrier();
+ }
+ `
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/alias_analysis.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/alias_analysis.spec.ts
index ba39485449..7efad7e798 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/alias_analysis.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/alias_analysis.spec.ts
@@ -38,50 +38,284 @@ function shouldPass(aliased: boolean, ...uses: UseName[]): boolean {
return !aliased || !uses.some(u => kUses[u].is_write) || uses.includes('no_access');
}
+type AddressSpace = 'private' | 'function' | 'storage' | 'uniform' | 'workgroup';
+
+const kWritableAddressSpaces = ['private', 'function', 'storage', 'workgroup'] as const;
+
+function ptr(addressSpace: AddressSpace, type: string) {
+ switch (addressSpace) {
+ case 'function':
+ return `ptr<function, ${type}>`;
+ case 'private':
+ return `ptr<private, ${type}>`;
+ case 'storage':
+ return `ptr<storage, ${type}, read_write>`;
+ case 'uniform':
+ return `ptr<uniform, ${type}>`;
+ case 'workgroup':
+ return `ptr<workgroup, ${type}>`;
+ }
+}
+
+function declareModuleScopeVar(
+ name: string,
+ addressSpace: 'private' | 'storage' | 'uniform' | 'workgroup',
+ type: string
+) {
+ const binding = name === 'x' ? 0 : 1;
+ switch (addressSpace) {
+ case 'private':
+ return `var<private> ${name} : ${type};`;
+ case 'storage':
+ return `@binding(${binding}) @group(0) var<storage, read_write> ${name} : ${type};`;
+ case 'uniform':
+ return `@binding(${binding}) @group(0) var<uniform> ${name} : ${type};`;
+ case 'workgroup':
+ return `var<workgroup> ${name} : ${type};`;
+ }
+}
+
+function maybeDeclareModuleScopeVar(name: string, addressSpace: AddressSpace, type: string) {
+ if (addressSpace === 'function') {
+ return '';
+ }
+ return declareModuleScopeVar(name, addressSpace, type);
+}
+
+function maybeDeclareFunctionScopeVar(name: string, addressSpace: AddressSpace, type: string) {
+ switch (addressSpace) {
+ case 'function':
+ return `var ${name} : ${type};`;
+ default:
+ return ``;
+ }
+}
+
+/**
+ * @returns true if a pointer of the given address space requires the
+ * 'unrestricted_pointer_parameters' language feature.
+ */
+function requiresUnrestrictedPointerParameters(addressSpace: AddressSpace) {
+ return addressSpace !== 'function' && addressSpace !== 'private';
+}
+
g.test('two_pointers')
.desc(`Test aliasing of two pointers passed to a function.`)
.params(u =>
u
- .combine('address_space', ['private', 'function'] as const)
+ .combine('address_space', kWritableAddressSpaces)
+ .combine('aliased', [true, false])
+ .beginSubcases()
.combine('a_use', keysOf(kUses))
.combine('b_use', keysOf(kUses))
+ )
+ .fn(t => {
+ if (requiresUnrestrictedPointerParameters(t.params.address_space)) {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+ }
+
+ const code = `
+${maybeDeclareModuleScopeVar('x', t.params.address_space, 'i32')}
+${maybeDeclareModuleScopeVar('y', t.params.address_space, 'i32')}
+
+fn callee(pa : ${ptr(t.params.address_space, 'i32')},
+ pb : ${ptr(t.params.address_space, 'i32')}) -> i32 {
+ ${kUses[t.params.a_use].gen(`*pa`)}
+ ${kUses[t.params.b_use].gen(`*pb`)}
+ return 0;
+}
+
+fn caller() {
+ ${maybeDeclareFunctionScopeVar('x', t.params.address_space, 'i32')}
+ ${maybeDeclareFunctionScopeVar('y', t.params.address_space, 'i32')}
+ callee(&x, ${t.params.aliased ? `&x` : `&y`});
+}
+`;
+ t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
+ });
+
+g.test('two_pointers_to_array_elements')
+ .desc(`Test aliasing of two array element pointers passed to a function.`)
+ .params(u =>
+ u
+ .combine('address_space', kWritableAddressSpaces)
+ .combine('index', [0, 1])
.combine('aliased', [true, false])
.beginSubcases()
+ .combine('a_use', keysOf(kUses))
+ .combine('b_use', keysOf(kUses))
)
.fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
const code = `
-${t.params.address_space === 'private' ? `var<private> x : i32; var<private> y : i32;` : ``}
+${maybeDeclareModuleScopeVar('x', t.params.address_space, 'array<i32, 4>')}
+${maybeDeclareModuleScopeVar('y', t.params.address_space, 'array<i32, 4>')}
-fn callee(pa : ptr<${t.params.address_space}, i32>,
- pb : ptr<${t.params.address_space}, i32>) -> i32 {
+fn callee(pa : ${ptr(t.params.address_space, 'i32')},
+ pb : ${ptr(t.params.address_space, 'i32')}) -> i32 {
${kUses[t.params.a_use].gen(`*pa`)}
${kUses[t.params.b_use].gen(`*pb`)}
return 0;
}
fn caller() {
- ${t.params.address_space === 'function' ? `var x : i32; var y : i32;` : ``}
- callee(&x, ${t.params.aliased ? `&x` : `&y`});
+ ${maybeDeclareFunctionScopeVar('x', t.params.address_space, 'array<i32, 4>')}
+ ${maybeDeclareFunctionScopeVar('y', t.params.address_space, 'array<i32, 4>')}
+ callee(&x[${t.params.index}], ${t.params.aliased ? `&x[0]` : `&y[0]`});
}
`;
t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
});
-g.test('one_pointer_one_module_scope')
- .desc(`Test aliasing of a pointer with a direct access to a module-scope variable.`)
+g.test('two_pointers_to_array_elements_indirect')
+ .desc(
+ `Test aliasing of two array pointers passed to a function, which indexes those arrays and then
+passes the element pointers to another function.`
+ )
.params(u =>
u
+ .combine('address_space', kWritableAddressSpaces)
+ .combine('index', [0, 1])
+ .combine('aliased', [true, false])
+ .beginSubcases()
.combine('a_use', keysOf(kUses))
.combine('b_use', keysOf(kUses))
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const code = `
+${maybeDeclareModuleScopeVar('x', t.params.address_space, 'array<i32, 4>')}
+${maybeDeclareModuleScopeVar('y', t.params.address_space, 'array<i32, 4>')}
+
+fn callee(pa : ${ptr(t.params.address_space, 'i32')},
+ pb : ${ptr(t.params.address_space, 'i32')}) -> i32 {
+ ${kUses[t.params.a_use].gen(`*pa`)}
+ ${kUses[t.params.b_use].gen(`*pb`)}
+ return 0;
+}
+
+fn index(pa : ${ptr(t.params.address_space, 'array<i32, 4>')},
+ pb : ${ptr(t.params.address_space, 'array<i32, 4>')}) -> i32 {
+ return callee(&(*pa)[${t.params.index}], &(*pb)[0]);
+}
+
+fn caller() {
+ ${maybeDeclareFunctionScopeVar('x', t.params.address_space, 'array<i32, 4>')}
+ ${maybeDeclareFunctionScopeVar('y', t.params.address_space, 'array<i32, 4>')}
+ index(&x, ${t.params.aliased ? `&x` : `&y`});
+}
+`;
+ t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
+ });
+
+g.test('two_pointers_to_struct_members')
+ .desc(`Test aliasing of two struct member pointers passed to a function.`)
+ .params(u =>
+ u
+ .combine('address_space', kWritableAddressSpaces)
+ .combine('member', ['a', 'b'])
+ .combine('aliased', [true, false])
+ .beginSubcases()
+ .combine('a_use', keysOf(kUses))
+ .combine('b_use', keysOf(kUses))
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const code = `
+struct S {
+ a : i32,
+ b : i32,
+}
+
+${maybeDeclareModuleScopeVar('x', t.params.address_space, 'S')}
+${maybeDeclareModuleScopeVar('y', t.params.address_space, 'S')}
+
+fn callee(pa : ${ptr(t.params.address_space, 'i32')},
+ pb : ${ptr(t.params.address_space, 'i32')}) -> i32 {
+ ${kUses[t.params.a_use].gen(`*pa`)}
+ ${kUses[t.params.b_use].gen(`*pb`)}
+ return 0;
+}
+
+fn caller() {
+ ${maybeDeclareFunctionScopeVar('x', t.params.address_space, 'S')}
+ ${maybeDeclareFunctionScopeVar('y', t.params.address_space, 'S')}
+ callee(&x.${t.params.member}, ${t.params.aliased ? `&x.a` : `&y.a`});
+}
+`;
+ t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
+ });
+
+g.test('two_pointers_to_struct_members_indirect')
+ .desc(
+ `Test aliasing of two structure pointers passed to a function, which accesses members of those
+structures and then passes the member pointers to another function.`
+ )
+ .params(u =>
+ u
+ .combine('address_space', kWritableAddressSpaces)
+ .combine('member', ['a', 'b'])
+ .combine('aliased', [true, false])
+ .beginSubcases()
+ .combine('a_use', keysOf(kUses))
+ .combine('b_use', keysOf(kUses))
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const code = `
+struct S {
+ a : i32,
+ b : i32,
+}
+
+${maybeDeclareModuleScopeVar('x', t.params.address_space, 'S')}
+${maybeDeclareModuleScopeVar('y', t.params.address_space, 'S')}
+
+fn callee(pa : ${ptr(t.params.address_space, 'i32')},
+ pb : ${ptr(t.params.address_space, 'i32')}) -> i32 {
+ ${kUses[t.params.a_use].gen(`*pa`)}
+ ${kUses[t.params.b_use].gen(`*pb`)}
+ return 0;
+}
+
+fn access(pa : ${ptr(t.params.address_space, 'S')},
+ pb : ${ptr(t.params.address_space, 'S')}) -> i32 {
+ return callee(&(*pa).${t.params.member}, &(*pb).a);
+}
+
+fn caller() {
+ ${maybeDeclareFunctionScopeVar('x', t.params.address_space, 'S')}
+ ${maybeDeclareFunctionScopeVar('y', t.params.address_space, 'S')}
+ access(&x, ${t.params.aliased ? `&x` : `&y`});
+}
+`;
+ t.expectCompileResult(shouldPass(t.params.aliased, t.params.a_use, t.params.b_use), code);
+ });
+
+g.test('one_pointer_one_module_scope')
+ .desc(`Test aliasing of a pointer with a direct access to a module-scope variable.`)
+ .params(u =>
+ u
+ .combine('address_space', ['private', 'storage', 'workgroup'] as const)
.combine('aliased', [true, false])
.beginSubcases()
+ .combine('a_use', keysOf(kUses))
+ .combine('b_use', keysOf(kUses))
)
.fn(t => {
+ if (requiresUnrestrictedPointerParameters(t.params.address_space)) {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+ }
+
const code = `
-var<private> x : i32;
-var<private> y : i32;
+${declareModuleScopeVar('x', t.params.address_space, 'i32')}
+${declareModuleScopeVar('y', t.params.address_space, 'i32')}
-fn callee(pb : ptr<private, i32>) -> i32 {
+fn callee(pb : ${ptr(t.params.address_space, 'i32')}) -> i32 {
${kUses[t.params.a_use].gen(`x`)}
${kUses[t.params.b_use].gen(`*pb`)}
return 0;
@@ -98,29 +332,34 @@ g.test('subcalls')
.desc(`Test aliasing of two pointers passed to a function, and then passed to other functions.`)
.params(u =>
u
- .combine('a_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
- .combine('b_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
+ .combine('address_space', ['private', 'storage', 'workgroup'] as const)
.combine('aliased', [true, false])
.beginSubcases()
+ .combine('a_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
+ .combine('b_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
)
.fn(t => {
+ if (requiresUnrestrictedPointerParameters(t.params.address_space)) {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+ }
+ const ptr_i32 = ptr(t.params.address_space, 'i32');
const code = `
-var<private> x : i32;
-var<private> y : i32;
+${declareModuleScopeVar('x', t.params.address_space, 'i32')}
+${declareModuleScopeVar('y', t.params.address_space, 'i32')}
-fn subcall_no_access(p : ptr<private, i32>) {
+fn subcall_no_access(p : ${ptr_i32}) {
let pp = &*p;
}
-fn subcall_binary_lhs(p : ptr<private, i32>) -> i32 {
+fn subcall_binary_lhs(p : ${ptr_i32}) -> i32 {
return *p + 1;
}
-fn subcall_assign(p : ptr<private, i32>) {
+fn subcall_assign(p : ${ptr_i32}) {
*p = 42;
}
-fn callee(pa : ptr<private, i32>, pb : ptr<private, i32>) -> i32 {
+fn callee(pa : ${ptr_i32}, pb : ${ptr_i32}) -> i32 {
let new_pa = &*pa;
let new_pb = &*pb;
subcall_${t.params.a_use}(new_pa);
@@ -139,20 +378,25 @@ g.test('member_accessors')
.desc(`Test aliasing of two pointers passed to a function and used with member accessors.`)
.params(u =>
u
- .combine('a_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
- .combine('b_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
+ .combine('address_space', ['private', 'storage', 'workgroup'] as const)
.combine('aliased', [true, false])
.beginSubcases()
+ .combine('a_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
+ .combine('b_use', ['no_access', 'assign', 'binary_lhs'] as UseName[])
)
.fn(t => {
+ if (requiresUnrestrictedPointerParameters(t.params.address_space)) {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+ }
+
+ const ptr_S = ptr(t.params.address_space, 'S');
const code = `
struct S { a : i32 }
-var<private> x : S;
-var<private> y : S;
+${declareModuleScopeVar('x', t.params.address_space, 'S')}
+${declareModuleScopeVar('y', t.params.address_space, 'S')}
-fn callee(pa : ptr<private, S>,
- pb : ptr<private, S>) -> i32 {
+fn callee(pa : ${ptr_S}, pb : ${ptr_S}) -> i32 {
${kUses[t.params.a_use].gen(`(*pa).a`)}
${kUses[t.params.b_use].gen(`(*pb).a`)}
return 0;
@@ -167,12 +411,18 @@ fn caller() {
g.test('same_pointer_read_and_write')
.desc(`Test that we can read from and write to the same pointer.`)
- .params(u => u.beginSubcases())
+ .params(u =>
+ u.combine('address_space', ['private', 'storage', 'workgroup'] as const).beginSubcases()
+ )
.fn(t => {
+ if (requiresUnrestrictedPointerParameters(t.params.address_space)) {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+ }
+
const code = `
-var<private> v : i32;
+${declareModuleScopeVar('v', t.params.address_space, 'i32')}
-fn callee(p : ptr<private, i32>) {
+fn callee(p : ${ptr(t.params.address_space, 'i32')}) {
*p = *p + 1;
}
@@ -185,10 +435,12 @@ fn caller() {
g.test('aliasing_inside_function')
.desc(`Test that we can alias pointers inside a function.`)
- .params(u => u.beginSubcases())
+ .params(u =>
+ u.combine('address_space', ['private', 'storage', 'workgroup'] as const).beginSubcases()
+ )
.fn(t => {
const code = `
-var<private> v : i32;
+${declareModuleScopeVar('v', t.params.address_space, 'i32')}
fn foo() {
var v : i32;
@@ -200,3 +452,236 @@ fn foo() {
`;
t.expectCompileResult(true, code);
});
+
+const kAtomicBuiltins = [
+ 'atomicLoad',
+ 'atomicStore',
+ 'atomicAdd',
+ 'atomicSub',
+ 'atomicMax',
+ 'atomicMin',
+ 'atomicAnd',
+ 'atomicOr',
+ 'atomicXor',
+ 'atomicExchange',
+ 'atomicCompareExchangeWeak',
+] as const;
+
+type AtomicBuiltins = (typeof kAtomicBuiltins)[number];
+
+function isWrite(builtin: AtomicBuiltins) {
+ switch (builtin) {
+ case 'atomicLoad':
+ return false;
+ case 'atomicAdd':
+ case 'atomicSub':
+ case 'atomicMax':
+ case 'atomicMin':
+ case 'atomicAnd':
+ case 'atomicOr':
+ case 'atomicXor':
+ case 'atomicExchange':
+ case 'atomicCompareExchangeWeak':
+ case 'atomicStore':
+ return true;
+ }
+}
+
+function callAtomicBuiltin(builtin: AtomicBuiltins, ptr: string) {
+ switch (builtin) {
+ case 'atomicLoad':
+ return `i += ${builtin}(${ptr})`;
+ case 'atomicStore':
+ return `${builtin}(${ptr}, 42)`;
+ case 'atomicAdd':
+ case 'atomicSub':
+ case 'atomicMax':
+ case 'atomicMin':
+ case 'atomicAnd':
+ case 'atomicOr':
+ case 'atomicXor':
+ case 'atomicExchange':
+ return `i += ${builtin}(${ptr}, 42)`;
+ case 'atomicCompareExchangeWeak':
+ return `${builtin}(${ptr}, 10, 42)`;
+ }
+}
+
+g.test('two_atomic_pointers')
+ .desc(`Test aliasing of two atomic pointers passed to a function.`)
+ .params(u =>
+ u
+ .combine('builtin_a', kAtomicBuiltins)
+ .combine('builtin_b', ['atomicLoad', 'atomicStore'] as const)
+ .combine('address_space', ['storage', 'workgroup'] as const)
+ .combine('aliased', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const ptr_atomic_i32 = ptr(t.params.address_space, 'atomic<i32>');
+ const code = `
+${declareModuleScopeVar('x', t.params.address_space, 'atomic<i32>')}
+${declareModuleScopeVar('y', t.params.address_space, 'atomic<i32>')}
+
+fn callee(pa : ${ptr_atomic_i32}, pb : ${ptr_atomic_i32}) {
+ var i : i32;
+ ${callAtomicBuiltin(t.params.builtin_a, 'pa')};
+ ${callAtomicBuiltin(t.params.builtin_b, 'pb')};
+}
+
+fn caller() {
+ callee(&x, &${t.params.aliased ? 'x' : 'y'});
+}
+`;
+ const shouldFail =
+ t.params.aliased && (isWrite(t.params.builtin_a) || isWrite(t.params.builtin_b));
+ t.expectCompileResult(!shouldFail, code);
+ });
+
+g.test('two_atomic_pointers_to_array_elements')
+ .desc(`Test aliasing of two atomic array element pointers passed to a function.`)
+ .params(u =>
+ u
+ .combine('builtin_a', kAtomicBuiltins)
+ .combine('builtin_b', ['atomicLoad', 'atomicStore'] as const)
+ .combine('address_space', ['storage', 'workgroup'] as const)
+ .combine('index', [0, 1])
+ .combine('aliased', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const ptr_atomic_i32 = ptr(t.params.address_space, 'atomic<i32>');
+ const code = `
+${declareModuleScopeVar('x', t.params.address_space, 'array<atomic<i32>, 32>')}
+${declareModuleScopeVar('y', t.params.address_space, 'array<atomic<i32>, 32>')}
+
+fn callee(pa : ${ptr_atomic_i32}, pb : ${ptr_atomic_i32}) {
+ var i : i32;
+ ${callAtomicBuiltin(t.params.builtin_a, 'pa')};
+ ${callAtomicBuiltin(t.params.builtin_b, 'pb')};
+}
+
+fn caller() {
+ callee(&x[${t.params.index}], &${t.params.aliased ? 'x' : 'y'}[0]);
+}
+`;
+ const shouldFail =
+ t.params.aliased && (isWrite(t.params.builtin_a) || isWrite(t.params.builtin_b));
+ t.expectCompileResult(!shouldFail, code);
+ });
+
+g.test('two_atomic_pointers_to_struct_members')
+ .desc(`Test aliasing of two struct member atomic pointers passed to a function.`)
+ .params(u =>
+ u
+ .combine('builtin_a', kAtomicBuiltins)
+ .combine('builtin_b', ['atomicLoad', 'atomicStore'] as const)
+ .combine('address_space', ['storage', 'workgroup'] as const)
+ .combine('member', ['a', 'b'])
+ .combine('aliased', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const ptr_atomic_i32 = ptr(t.params.address_space, 'atomic<i32>');
+ const code = `
+struct S {
+ a : atomic<i32>,
+ b : atomic<i32>,
+}
+
+${declareModuleScopeVar('x', t.params.address_space, 'S')}
+${declareModuleScopeVar('y', t.params.address_space, 'S')}
+
+fn callee(pa : ${ptr_atomic_i32}, pb : ${ptr_atomic_i32}) {
+ var i : i32;
+ ${callAtomicBuiltin(t.params.builtin_a, 'pa')};
+ ${callAtomicBuiltin(t.params.builtin_b, 'pb')};
+}
+
+fn caller() {
+ callee(&x.${t.params.member}, &${t.params.aliased ? 'x' : 'y'}.a);
+}
+`;
+ const shouldFail =
+ t.params.aliased && (isWrite(t.params.builtin_a) || isWrite(t.params.builtin_b));
+ t.expectCompileResult(!shouldFail, code);
+ });
+
+g.test('one_atomic_pointer_one_module_scope')
+ .desc(`Test aliasing of an atomic pointer with a direct access to a module-scope variable.`)
+ .params(u =>
+ u
+ .combine('builtin_a', kAtomicBuiltins)
+ .combine('builtin_b', ['atomicLoad', 'atomicStore'] as const)
+ .combine('address_space', ['storage', 'workgroup'] as const)
+ .combine('aliased', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ const ptr_atomic_i32 = ptr(t.params.address_space, 'atomic<i32>');
+ const code = `
+${declareModuleScopeVar('x', t.params.address_space, 'atomic<i32>')}
+${declareModuleScopeVar('y', t.params.address_space, 'atomic<i32>')}
+
+fn callee(p : ${ptr_atomic_i32}) {
+ var i : i32;
+ ${callAtomicBuiltin(t.params.builtin_a, 'p')};
+ ${callAtomicBuiltin(t.params.builtin_b, t.params.aliased ? '&x' : '&y')};
+}
+
+fn caller() {
+ callee(&x);
+}
+`;
+ const shouldFail =
+ t.params.aliased && (isWrite(t.params.builtin_a) || isWrite(t.params.builtin_b));
+ t.expectCompileResult(!shouldFail, code);
+ });
+
+g.test('workgroup_uniform_load')
+ .desc(`Test aliasing via workgroupUniformLoad.`)
+ .params(u =>
+ u
+ .combine('use', ['load', 'store', 'workgroupUniformLoad'] as const)
+ .combine('aliased', [true, false])
+ .beginSubcases()
+ )
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('unrestricted_pointer_parameters');
+
+ function emitUse() {
+ switch (t.params.use) {
+ case 'load':
+ return `v = *pa`;
+ case 'store':
+ return `*pa = 1`;
+ case 'workgroupUniformLoad':
+ return `v = workgroupUniformLoad(pa)`;
+ }
+ }
+
+ const code = `
+var<workgroup> x : i32;
+var<workgroup> y : i32;
+
+fn callee(pa : ptr<workgroup, i32>, pb : ptr<workgroup, i32>) -> i32 {
+ var v : i32;
+ ${emitUse()};
+ return v + workgroupUniformLoad(pb);
+}
+
+fn caller() {
+ callee(&x, &${t.params.aliased ? 'x' : 'y'});
+}
+`;
+ const shouldFail = t.params.aliased && t.params.use === 'store';
+ t.expectCompileResult(!shouldFail, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/restrictions.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/restrictions.spec.ts
index b6affd14d6..7c79e6c5ea 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/restrictions.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/functions/restrictions.spec.ts
@@ -12,6 +12,30 @@ interface VertexPosCase {
valid: boolean;
}
+const kCCommonTypeDecls = `
+struct runtime_array_struct {
+ arr : array<u32>
+}
+
+struct constructible {
+ a : i32,
+ b : u32,
+ c : f32,
+ d : bool,
+}
+
+struct host_shareable {
+ a : i32,
+ b : u32,
+ c : f32,
+}
+
+struct struct_with_array {
+ a : array<constructible, 4>
+}
+
+`;
+
const kVertexPosCases: Record<string, VertexPosCase> = {
bare_position: { name: `@builtin(position) vec4f`, value: `vec4f()`, valid: true },
nested_position: { name: `pos_struct`, value: `pos_struct()`, valid: true },
@@ -145,20 +169,7 @@ g.test('function_return_types')
const code = `
${enable}
-struct runtime_array_struct {
- arr : array<u32>
-}
-
-struct constructible {
- a : i32,
- b : u32,
- c : f32,
- d : bool,
-}
-
-struct struct_with_array {
- a : array<constructible, 4>
-}
+${kCCommonTypeDecls}
struct atomic_struct {
a : atomic<u32>
@@ -192,7 +203,7 @@ fn foo() -> ${testcase.name} {
interface ParamTypeCase {
name: string;
- valid: boolean;
+ valid: boolean | 'with_unrestricted_pointer_parameters';
}
const kFunctionParamTypeCases: Record<string, ParamTypeCase> = {
@@ -246,21 +257,42 @@ const kFunctionParamTypeCases: Record<string, ParamTypeCase> = {
ptr3: { name: `ptr<private, u32>`, valid: true },
ptr4: { name: `ptr<private, constructible>`, valid: true },
+ // Pointers only valid with unrestricted_pointer_parameters
+ ptr5: { name: `ptr<storage, u32>`, valid: 'with_unrestricted_pointer_parameters' },
+ ptr6: { name: `ptr<storage, u32, read>`, valid: 'with_unrestricted_pointer_parameters' },
+ ptr7: { name: `ptr<storage, u32, read_write>`, valid: 'with_unrestricted_pointer_parameters' },
+ ptr8: { name: `ptr<uniform, u32>`, valid: 'with_unrestricted_pointer_parameters' },
+ ptr9: { name: `ptr<workgroup, u32>`, valid: 'with_unrestricted_pointer_parameters' },
+ ptr10: {
+ name: `ptr<storage, host_shareable, read_write>`,
+ valid: 'with_unrestricted_pointer_parameters',
+ },
+ ptr11: {
+ name: `ptr<storage, host_shareable, read>`,
+ valid: 'with_unrestricted_pointer_parameters',
+ },
+ ptr12: {
+ name: `ptr<uniform, host_shareable>`,
+ valid: 'with_unrestricted_pointer_parameters',
+ },
+ ptrWorkgroupAtomic: {
+ name: `ptr<workgroup, atomic<u32>>`,
+ valid: 'with_unrestricted_pointer_parameters',
+ },
+ ptrWorkgroupNestedAtomic: {
+ name: `ptr<workgroup, array<atomic<u32>,1>>`,
+ valid: 'with_unrestricted_pointer_parameters',
+ },
+
// Invalid pointers.
- ptr5: { name: `ptr<storage, u32>`, valid: false },
- ptr6: { name: `ptr<storage, u32, read>`, valid: false },
- ptr7: { name: `ptr<storage, u32, read_write>`, valid: false },
- ptr8: { name: `ptr<uniform, u32>`, valid: false },
- ptr9: { name: `ptr<workgroup, u32>`, valid: false },
- ptr10: { name: `ptr<handle, u32>`, valid: false }, // Can't spell handle address space
- ptr12: { name: `ptr<not_an_address_space, u32>`, valid: false },
- ptr13: { name: `ptr<storage>`, valid: false }, // No store type
- ptr14: { name: `ptr<private,clamp>`, valid: false }, // Invalid store type
- ptr15: { name: `ptr<private,u32,read>`, valid: false }, // Can't specify access mode
- ptr16: { name: `ptr<private,u32,write>`, valid: false }, // Can't specify access mode
- ptr17: { name: `ptr<private,u32,read_write>`, valid: false }, // Can't specify access mode
- ptrWorkgroupAtomic: { name: `ptr<workgroup, atomic<u32>>`, valid: false },
- ptrWorkgroupNestedAtomic: { name: `ptr<workgroup, array<atomic<u32>,1>>`, valid: false },
+ invalid_ptr1: { name: `ptr<handle, u32>`, valid: false }, // Can't spell handle address space
+ invalid_ptr2: { name: `ptr<not_an_address_space, u32>`, valid: false },
+ invalid_ptr3: { name: `ptr<storage>`, valid: false }, // No store type
+ invalid_ptr4: { name: `ptr<private,u32,read>`, valid: false }, // Can't specify access mode
+ invalid_ptr5: { name: `ptr<private,u32,write>`, valid: false }, // Can't specify access mode
+ invalid_ptr6: { name: `ptr<private,u32,read_write>`, valid: false }, // Can't specify access mode
+ invalid_ptr7: { name: `ptr<private,clamp>`, valid: false }, // Invalid store type
+ invalid_ptr8: { name: `ptr<function, texture_external>`, valid: false }, // non-constructable pointer type
};
g.test('function_parameter_types')
@@ -278,30 +310,23 @@ g.test('function_parameter_types')
const code = `
${enable}
-struct runtime_array_struct {
- arr : array<u32>
-}
-
-struct constructible {
- a : i32,
- b : u32,
- c : f32,
- d : bool,
-}
-
-struct struct_with_array {
- a : array<constructible, 4>
-}
+${kCCommonTypeDecls}
fn foo(param : ${testcase.name}) {
}`;
- t.expectCompileResult(testcase.valid, code);
+ let isValid = testcase.valid;
+ if (isValid === 'with_unrestricted_pointer_parameters') {
+ isValid = t.hasLanguageFeature('unrestricted_pointer_parameters');
+ }
+
+ t.expectCompileResult(isValid, code);
});
interface ParamValueCase {
value: string;
matches: string[];
+ needsUnrestrictedPointerParameters?: boolean;
}
const kFunctionParamValueCases: Record<string, ParamValueCase> = {
@@ -426,15 +451,47 @@ const kFunctionParamValueCases: Record<string, ParamValueCase> = {
ptr3: { value: `&g_u32`, matches: ['ptr3'] },
ptr4: { value: `&g_constructible`, matches: ['ptr4'] },
- // Invalid pointers
- ptr5: { value: `&f_constructible.b`, matches: [] },
- ptr6: { value: `&g_constructible.b`, matches: [] },
- ptr7: { value: `&f_struct_with_array.a[1].b`, matches: [] },
- ptr8: { value: `&g_struct_with_array.a[2]`, matches: [] },
- ptr9: { value: `&ro_constructible.b`, matches: [] },
- ptr10: { value: `&rw_constructible`, matches: [] },
- ptr11: { value: `&uniform_constructible`, matches: [] },
- ptr12: { value: `&ro_constructible`, matches: [] },
+ // Requires 'unrestricted_pointer_parameters' WGSL feature
+ ptr5: {
+ value: `&f_constructible.b`,
+ matches: ['ptr1'],
+ needsUnrestrictedPointerParameters: true,
+ },
+ ptr6: {
+ value: `&g_constructible.b`,
+ matches: ['ptr3'],
+ needsUnrestrictedPointerParameters: true,
+ },
+ ptr7: {
+ value: `&f_struct_with_array.a[1].b`,
+ matches: ['ptr1'],
+ needsUnrestrictedPointerParameters: true,
+ },
+ ptr8: {
+ value: `&g_struct_with_array.a[2]`,
+ matches: ['ptr4'],
+ needsUnrestrictedPointerParameters: true,
+ },
+ ptr9: {
+ value: `&ro_host_shareable.b`,
+ matches: ['ptr5', 'ptr6'],
+ needsUnrestrictedPointerParameters: true,
+ },
+ ptr10: {
+ value: `&rw_host_shareable`,
+ matches: ['ptr10'],
+ needsUnrestrictedPointerParameters: true,
+ },
+ ptr11: {
+ value: `&ro_host_shareable`,
+ matches: ['ptr11'],
+ needsUnrestrictedPointerParameters: true,
+ },
+ ptr12: {
+ value: `&uniform_host_shareable`,
+ matches: ['ptr12'],
+ needsUnrestrictedPointerParameters: true,
+ },
};
function parameterMatches(decl: string, matches: string[]): boolean {
@@ -454,10 +511,11 @@ g.test('function_parameter_matching')
.params(u =>
u
.combine('decl', keysOf(kFunctionParamTypeCases))
- .combine('arg', keysOf(kFunctionParamValueCases))
.filter(u => {
- return kFunctionParamTypeCases[u.decl].valid;
+ return kFunctionParamTypeCases[u.decl].valid !== false;
})
+ .beginSubcases()
+ .combine('arg', keysOf(kFunctionParamValueCases))
)
.beforeAllSubcases(t => {
if (kFunctionParamTypeCases[t.params.decl].name === 'f16') {
@@ -471,26 +529,7 @@ g.test('function_parameter_matching')
const code = `
${enable}
-struct runtime_array_struct {
- arr : array<u32>
-}
-
-struct constructible {
- a : i32,
- b : u32,
- c : f32,
- d : bool,
-}
-
-struct host_shareable {
- a : i32,
- b : u32,
- c : f32,
-}
-
-struct struct_with_array {
- a : array<constructible, 4>
-}
+${kCCommonTypeDecls}
@group(0) @binding(0)
var t : texture_2d<f32>;
@group(0) @binding(1)
@@ -507,11 +546,11 @@ var t_multisampled : texture_multisampled_2d<f32>;
var t_external : texture_external;
@group(1) @binding(0)
-var<storage> ro_constructible : host_shareable;
+var<storage> ro_host_shareable : host_shareable;
@group(1) @binding(1)
-var<storage, read_write> rw_constructible : host_shareable;
+var<storage, read_write> rw_host_shareable : host_shareable;
@group(1) @binding(2)
-var<uniform> uniform_constructible : host_shareable;
+var<uniform> uniform_host_shareable : host_shareable;
fn bar(param : ${param.name}) { }
@@ -568,7 +607,17 @@ fn foo() {
}
`;
- t.expectCompileResult(parameterMatches(t.params.decl, arg.matches), code);
+ const needsUnrestrictedPointerParameters =
+ (kFunctionParamTypeCases[t.params.decl].valid === 'with_unrestricted_pointer_parameters' ||
+ arg.needsUnrestrictedPointerParameters) ??
+ false;
+
+ let isValid = parameterMatches(t.params.decl, arg.matches);
+ if (isValid && needsUnrestrictedPointerParameters) {
+ isValid = t.hasLanguageFeature('unrestricted_pointer_parameters');
+ }
+
+ t.expectCompileResult(isValid, code);
});
g.test('no_direct_recursion')
@@ -691,67 +740,75 @@ function checkArgTypeMatch(param_type: string, arg_matches: string[]): boolean {
return false;
}
-g.test('call_arg_types_match_params')
+g.test('call_arg_types_match_1_param')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-calls')
+ .desc(`Test that the argument types match in order`)
+ .params(u =>
+ u
+ .combine('p1_type', kParamsTypes) //
+ .beginSubcases()
+ .combine('arg1_value', keysOf(kArgValues))
+ )
+ .fn(t => {
+ const code = `
+fn bar(p1 : ${t.params.p1_type}) { }
+fn foo() {
+ bar(${kArgValues[t.params.arg1_value].value});
+}`;
+
+ const res = checkArgTypeMatch(t.params.p1_type, kArgValues[t.params.arg1_value].matches);
+ t.expectCompileResult(res, code);
+ });
+
+g.test('call_arg_types_match_2_params')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-calls')
+ .desc(`Test that the argument types match in order`)
+ .params(u =>
+ u
+ .combine('p1_type', kParamsTypes)
+ .combine('p2_type', kParamsTypes)
+ .beginSubcases()
+ .combine('arg1_value', keysOf(kArgValues))
+ .combine('arg2_value', keysOf(kArgValues))
+ )
+ .fn(t => {
+ const code = `
+fn bar(p1 : ${t.params.p1_type}, p2 : ${t.params.p2_type}) { }
+fn foo() {
+ bar(${kArgValues[t.params.arg1_value].value}, ${kArgValues[t.params.arg2_value].value});
+}`;
+
+ const res =
+ checkArgTypeMatch(t.params.p1_type, kArgValues[t.params.arg1_value].matches) &&
+ checkArgTypeMatch(t.params.p2_type, kArgValues[t.params.arg2_value].matches);
+ t.expectCompileResult(res, code);
+ });
+
+g.test('call_arg_types_match_3_params')
.specURL('https://gpuweb.github.io/gpuweb/wgsl/#function-calls')
.desc(`Test that the argument types match in order`)
.params(u =>
u
- .combine('num_args', [1, 2, 3] as const)
.combine('p1_type', kParamsTypes)
.combine('p2_type', kParamsTypes)
.combine('p3_type', kParamsTypes)
+ .beginSubcases()
.combine('arg1_value', keysOf(kArgValues))
.combine('arg2_value', keysOf(kArgValues))
.combine('arg3_value', keysOf(kArgValues))
)
.fn(t => {
- let code = `
- fn bar(`;
- for (let i = 0; i < t.params.num_args; i++) {
- switch (i) {
- case 0:
- default: {
- code += `p${i} : ${t.params.p1_type},`;
- break;
- }
- case 1: {
- code += `p${i} : ${t.params.p2_type},`;
- break;
- }
- case 2: {
- code += `p${i} : ${t.params.p3_type},`;
- break;
- }
- }
- }
- code += `) { }
- fn foo() {
- bar(`;
- for (let i = 0; i < t.params.num_args; i++) {
- switch (i) {
- case 0:
- default: {
- code += `${kArgValues[t.params.arg1_value].value},`;
- break;
- }
- case 1: {
- code += `${kArgValues[t.params.arg2_value].value},`;
- break;
- }
- case 2: {
- code += `${kArgValues[t.params.arg3_value].value},`;
- break;
- }
- }
- }
- code += `);\n}`;
+ const code = `
+fn bar(p1 : ${t.params.p1_type}, p2 : ${t.params.p2_type}, p3 : ${t.params.p3_type}) { }
+fn foo() {
+ bar(${kArgValues[t.params.arg1_value].value},
+ ${kArgValues[t.params.arg2_value].value},
+ ${kArgValues[t.params.arg3_value].value});
+}`;
- let res = checkArgTypeMatch(t.params.p1_type, kArgValues[t.params.arg1_value].matches);
- if (res && t.params.num_args > 1) {
- res = checkArgTypeMatch(t.params.p2_type, kArgValues[t.params.arg2_value].matches);
- }
- if (res && t.params.num_args > 2) {
- res = checkArgTypeMatch(t.params.p3_type, kArgValues[t.params.arg3_value].matches);
- }
+ const res =
+ checkArgTypeMatch(t.params.p1_type, kArgValues[t.params.arg1_value].matches) &&
+ checkArgTypeMatch(t.params.p2_type, kArgValues[t.params.arg2_value].matches) &&
+ checkArgTypeMatch(t.params.p3_type, kArgValues[t.params.arg3_value].matches);
t.expectCompileResult(res, code);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/break.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/break.spec.ts
index 7c0f067140..46074ba5d0 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/break.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/break.spec.ts
@@ -15,10 +15,6 @@ const kTests = {
src: 'loop { if true { break; } }',
pass: true,
},
- continuing_break_if: {
- src: 'loop { continuing { break if (true); } }',
- pass: true,
- },
while_break: {
src: 'while true { break; }',
pass: true,
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/break_if.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/break_if.spec.ts
new file mode 100644
index 0000000000..97a625f625
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/break_if.spec.ts
@@ -0,0 +1,141 @@
+export const description = `Validation tests for break if`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kTests = {
+ compound_break: {
+ src: '{ break if true; }',
+ pass: false,
+ },
+ loop_break: {
+ src: 'loop { break if true; }',
+ pass: false,
+ },
+ loop_if_break: {
+ src: 'loop { if true { break if false; } }',
+ pass: false,
+ },
+ continuing_break_if: {
+ src: 'loop { continuing { break if true; } }',
+ pass: true,
+ },
+ continuing_break_if_parens: {
+ src: 'loop { continuing { break if (true); } }',
+ pass: true,
+ },
+ continuing_break_if_not_last: {
+ src: 'loop { continuing { break if (true); let a = 4;} }',
+ pass: false,
+ },
+ while_break: {
+ src: 'while true { break if true; }',
+ pass: false,
+ },
+ while_if_break: {
+ src: 'while true { if true { break if true; } }',
+ pass: false,
+ },
+ for_break: {
+ src: 'for (;;) { break if true; }',
+ pass: false,
+ },
+ for_if_break: {
+ src: 'for (;;) { if true { break if true; } }',
+ pass: false,
+ },
+ switch_case_break: {
+ src: 'switch(1) { default: { break if true; } }',
+ pass: false,
+ },
+ switch_case_if_break: {
+ src: 'switch(1) { default: { if true { break if true; } } }',
+ pass: false,
+ },
+ break: {
+ src: 'break if true;',
+ pass: false,
+ },
+ return_break: {
+ src: 'return break if true;',
+ pass: false,
+ },
+ if_break: {
+ src: 'if true { break if true; }',
+ pass: false,
+ },
+ continuing_if_break: {
+ src: 'loop { continuing { if (true) { break if true; } } }',
+ pass: false,
+ },
+ switch_break: {
+ src: 'switch(1) { break if true; }',
+ pass: false,
+ },
+};
+
+g.test('placement')
+ .desc('Test that break if placement is validated correctly')
+ .params(u => u.combine('stmt', keysOf(kTests)))
+ .fn(t => {
+ const code = `
+@vertex
+fn vtx() -> @builtin(position) vec4f {
+ ${kTests[t.params.stmt].src}
+ return vec4f(1);
+}
+ `;
+ t.expectCompileResult(kTests[t.params.stmt].pass, code);
+ });
+
+const vec_types = [2, 3, 4]
+ .map(i => ['i32', 'u32', 'f32', 'f16'].map(j => `vec${i}<${j}>`))
+ .reduce((a, c) => a.concat(c), []);
+const f32_matrix_types = [2, 3, 4]
+ .map(i => [2, 3, 4].map(j => `mat${i}x${j}f`))
+ .reduce((a, c) => a.concat(c), []);
+const f16_matrix_types = [2, 3, 4]
+ .map(i => [2, 3, 4].map(j => `mat${i}x${j}<f16>`))
+ .reduce((a, c) => a.concat(c), []);
+
+g.test('non_bool_param')
+ .desc('Test that break if fails with a non-bool parameter')
+ .params(u =>
+ u.combine('type', [
+ 'f32',
+ 'f16',
+ 'i32',
+ 'u32',
+ 'S',
+ ...vec_types,
+ ...f32_matrix_types,
+ ...f16_matrix_types,
+ ])
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type.includes('f16')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const code = `
+struct S {
+ a: i32,
+}
+
+@vertex
+fn vtx() -> @builtin(position) vec4f {
+ var v: ${t.params.type};
+
+ loop {
+ continuing {
+ break if v;
+ }
+ }
+ return vec4f(1);
+}`;
+ t.expectCompileResult(t.params.type === 'bool', code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/compound.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/compound.spec.ts
new file mode 100644
index 0000000000..b3627c2e5b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/compound.spec.ts
@@ -0,0 +1,52 @@
+export const description = `Validation tests for compound statements`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kTests = {
+ missing_start: {
+ src: '}',
+ pass: false,
+ },
+ missing_end: {
+ src: '{',
+ pass: false,
+ },
+ empty: {
+ src: '{}',
+ pass: true,
+ },
+ semicolon: {
+ src: '{;}',
+ pass: true,
+ },
+ semicolons: {
+ src: '{;;}',
+ pass: true,
+ },
+ decl: {
+ src: '{const c = 1;}',
+ pass: true,
+ },
+ nested: {
+ src: '{ {} }',
+ pass: true,
+ },
+};
+
+g.test('parse')
+ .desc('Test that compound statments parse')
+ .params(u => u.combine('stmt', keysOf(kTests)))
+ .fn(t => {
+ const code = `
+@vertex
+fn vtx() -> @builtin(position) vec4f {
+ ${kTests[t.params.stmt].src}
+ return vec4f(1);
+}
+ `;
+ t.expectCompileResult(kTests[t.params.stmt].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/continuing.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/continuing.spec.ts
new file mode 100644
index 0000000000..7b53e4eab8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/continuing.spec.ts
@@ -0,0 +1,185 @@
+export const description = `Validation tests for continuing`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kTests = {
+ continuing_break_if: {
+ src: 'loop { continuing { break if true; } }',
+ pass: true,
+ },
+ continuing_empty: {
+ src: 'loop { if a == 4 { break; } continuing { } }',
+ pass: true,
+ },
+ continuing_break_if_parens: {
+ src: 'loop { continuing { break if (true); } }',
+ pass: true,
+ },
+ continuing_discard: {
+ src: 'loop { if a == 4 { break; } continuing { discard; } }',
+ pass: true,
+ },
+ continuing_continue_nested: {
+ src: 'loop { if a == 4 { break; } continuing { loop { if a == 4 { break; } continue; } } }',
+ pass: true,
+ },
+ continuing_continue: {
+ src: 'loop { if a == 4 { break; } continuing { continue; } }',
+ pass: false,
+ },
+ continuing_break: {
+ src: 'loop { continuing { break; } }',
+ pass: false,
+ },
+ continuing_for: {
+ src: 'loop { if a == 4 { break; } continuing { for(;a < 4;) { } } }',
+ pass: true,
+ },
+ continuing_for_break: {
+ src: 'loop { if a == 4 { break; } continuing { for(;;) { break; } } }',
+ pass: true,
+ },
+ continuing_while: {
+ src: 'loop { if a == 4 { break; } continuing { while a < 4 { } } }',
+ pass: true,
+ },
+ continuing_while_break: {
+ src: 'loop { if a == 4 { break; } continuing { while true { break; } } }',
+ pass: true,
+ },
+ continuing_semicolon: {
+ src: 'loop { if a == 4 { break; } continuing { ; } }',
+ pass: true,
+ },
+ continuing_functionn_call: {
+ src: 'loop { if a == 4 { break; } continuing { _ = b(); } }',
+ pass: true,
+ },
+ continuing_let: {
+ src: 'loop { if a == 4 { break; } continuing { let c = b(); } }',
+ pass: true,
+ },
+ continuing_var: {
+ src: 'loop { if a == 4 { break; } continuing { var a = b(); } }',
+ pass: true,
+ },
+ continuing_const: {
+ src: 'loop { if a == 4 { break; } continuing { const a = 1; } }',
+ pass: true,
+ },
+ continuing_block: {
+ src: 'loop { if a == 4 { break; } continuing { { } } }',
+ pass: true,
+ },
+ continuing_const_assert: {
+ src: 'loop { if a == 4 { break; } continuing { const_assert(1 != 2); } }',
+ pass: true,
+ },
+ continuing_loop: {
+ src: 'loop { if a == 4 { break; } continuing { loop { break; } } }',
+ pass: true,
+ },
+ continuing_if: {
+ src: 'loop { if a == 4 { break; } continuing { if true { } else if false { } else { } } }',
+ pass: true,
+ },
+ continuing_switch: {
+ src: 'loop { if a == 4 { break; } continuing { switch 2 { default: { } } } }',
+ pass: true,
+ },
+ continuing_switch_break: {
+ src: 'loop { if a == 4 { break; } continuing { switch 2 { default: { break; } } } }',
+ pass: true,
+ },
+ continuing_loop_nested_continuing: {
+ src: 'loop { if a == 4 { break; } continuing { loop { if a == 4 { break; } continuing { } } } }',
+ pass: true,
+ },
+ continuing_inc: {
+ src: 'loop { if a == 4 { break; } continuing { a += 1; } }',
+ pass: true,
+ },
+ continuing_dec: {
+ src: 'loop { if a == 4 { break; } continuing { a -= 1; } }',
+ pass: true,
+ },
+ while: {
+ src: 'while a < 4 { continuing { break if true; } }',
+ pass: false,
+ },
+ for: {
+ src: 'for (;a < 4;) { continuing { break if true; } }',
+ pass: false,
+ },
+ switch_case: {
+ src: 'switch(1) { default: { continuing { break if true; } } }',
+ pass: false,
+ },
+ switch: {
+ src: 'switch(1) { continuing { break if true; } }',
+ pass: false,
+ },
+ continuing: {
+ src: 'continuing { break if true; }',
+ pass: false,
+ },
+ return: {
+ src: 'return continuing { break if true; }',
+ pass: false,
+ },
+ if_body: {
+ src: 'if true { continuing { break if true; } }',
+ pass: false,
+ },
+ if: {
+ src: 'if true { } continuing { break if true; } }',
+ pass: false,
+ },
+ if_else: {
+ src: 'if true { } else { } continuing { break if true; } }',
+ pass: false,
+ },
+ continuing_continuing: {
+ src: 'loop { if a == 4 { break; } continuing { continuing { break if true; } } }',
+ pass: false,
+ },
+ no_body: {
+ src: 'loop { if a == 4 { break; } continuing }',
+ pass: false,
+ },
+ return_in_continue: {
+ src: 'loop { if a == 4 { break; } continuing { return vec4f(2); } }',
+ pass: false,
+ },
+ return_if_nested_in_continue: {
+ src: 'loop { if a == 4 { break; } continuing { if true { return vec4f(2); } } }',
+ pass: false,
+ },
+ return_for_nested_in_continue: {
+ src: 'loop { if a == 4 { break; } continuing { for(;a < 4;) { return vec4f(2); } } }',
+ pass: false,
+ },
+};
+
+g.test('placement')
+ .desc('Test that continuing placement is validated correctly')
+ .params(u => u.combine('stmt', keysOf(kTests)))
+ .fn(t => {
+ const code = `
+fn b() -> i32 {
+ return 1;
+}
+
+@fragment
+fn frag() -> @location(0) vec4f {
+ var a = 0;
+ ${kTests[t.params.stmt].src}
+ return vec4f(1);
+}
+ `;
+ t.expectCompileResult(kTests[t.params.stmt].pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/diagnostic.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/diagnostic.spec.ts
index 154a4253ea..701ae46f3a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/diagnostic.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/diagnostic.spec.ts
@@ -199,3 +199,263 @@ g.test('conflicting_attribute_different_location')
const code = `${kNestedLocations[t.params.loc](d1, d2)}`;
t.expectCompileResult(true, code);
});
+
+g.test('after_other_directives')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics')
+ .desc(`Tests other global directives before a diagnostic directive.`)
+ .params(u =>
+ u.combine('directive', ['enable f16', 'requires readonly_and_readwrite_storage_textures'])
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.directive.startsWith('enable')) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ if (t.params.directive.startsWith('requires')) {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+ }
+
+ let code = `${t.params.directive};`;
+ code += generateDiagnostic('directive', 'info', 'derivative_uniformity') + ';';
+ t.expectCompileResult(true, code);
+ });
+
+interface ScopeCase {
+ code: string;
+ result: boolean | 'warn';
+}
+
+function scopeCode(body: string): string {
+ return `
+@group(0) @binding(0) var t : texture_1d<f32>;
+@group(0) @binding(1) var s : sampler;
+var<private> non_uniform_cond : bool;
+var<private> non_uniform_coord : f32;
+var<private> non_uniform_val : u32;
+@fragment fn main() {
+ ${body}
+}
+`;
+}
+
+const kScopeCases: Record<string, ScopeCase> = {
+ override_global_off: {
+ code: `
+ ${generateDiagnostic('directive', 'error', 'derivative_uniformity')};
+ ${scopeCode(`
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')}
+ if non_uniform_cond {
+ _ = textureSample(t,s,0.0);
+ }`)};
+ `,
+ result: true,
+ },
+ override_global_on: {
+ code: `
+ ${generateDiagnostic('directive', 'off', 'derivative_uniformity')};
+ ${scopeCode(`
+ ${generateDiagnostic('', 'error', 'derivative_uniformity')}
+ if non_uniform_cond {
+ _ = textureSample(t,s,0.0);
+ }`)}
+ `,
+ result: false,
+ },
+ override_global_warn: {
+ code: `
+ ${generateDiagnostic('directive', 'error', 'derivative_uniformity')};
+ ${scopeCode(`
+ ${generateDiagnostic('', 'warning', 'derivative_uniformity')}
+ if non_uniform_cond {
+ _ = textureSample(t,s,0.0);
+ }`)}
+ `,
+ result: 'warn',
+ },
+ global_if_nothing_else_warn: {
+ code: `
+ ${generateDiagnostic('directive', 'warning', 'derivative_uniformity')};
+ ${scopeCode(`
+ if non_uniform_cond {
+ _ = textureSample(t,s,0.0);
+ }`)}
+ `,
+ result: 'warn',
+ },
+ deepest_nesting_warn: {
+ code: scopeCode(`
+ ${generateDiagnostic('', 'error', 'derivative_uniformity')}
+ if non_uniform_cond {
+ ${generateDiagnostic('', 'warning', 'derivative_uniformity')}
+ if non_uniform_cond {
+ _ = textureSample(t,s,0.0);
+ }
+ }`),
+ result: 'warn',
+ },
+ deepest_nesting_off: {
+ code: scopeCode(`
+ ${generateDiagnostic('', 'error', 'derivative_uniformity')}
+ if non_uniform_cond {
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')}
+ if non_uniform_cond {
+ _ = textureSample(t,s,0.0);
+ }
+ }`),
+ result: true,
+ },
+ deepest_nesting_error: {
+ code: scopeCode(`
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')}
+ if non_uniform_cond {
+ ${generateDiagnostic('', 'error', 'derivative_uniformity')}
+ if non_uniform_cond {
+ _ = textureSample(t,s,0.0);
+ }
+ }`),
+ result: false,
+ },
+ other_nest_unaffected: {
+ code: `
+ ${generateDiagnostic('directive', 'warning', 'derivative_uniformity')};
+ ${scopeCode(`
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')}
+ if non_uniform_cond {
+ _ = textureSample(t,s,0.0);
+ }
+ if non_uniform_cond {
+ _ = textureSample(t,s,0.0);
+ }`)}
+ `,
+ result: 'warn',
+ },
+ deeper_nest_no_effect: {
+ code: `
+ ${generateDiagnostic('directive', 'error', 'derivative_uniformity')};
+ ${scopeCode(`
+ if non_uniform_cond {
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')}
+ if non_uniform_cond {
+ }
+ _ = textureSample(t,s,0.0);
+ }`)}
+ `,
+ result: false,
+ },
+ call_unaffected_error: {
+ code: `
+ ${generateDiagnostic('directive', 'error', 'derivative_uniformity')};
+ fn foo() { _ = textureSample(t,s,0.0); }
+ ${scopeCode(`
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')}
+ if non_uniform_cond {
+ foo();
+ }`)}
+ `,
+ result: false,
+ },
+ call_unaffected_warn: {
+ code: `
+ ${generateDiagnostic('directive', 'warning', 'derivative_uniformity')};
+ fn foo() { _ = textureSample(t,s,0.0); }
+ ${scopeCode(`
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')}
+ if non_uniform_cond {
+ foo();
+ }`)}
+ `,
+ result: 'warn',
+ },
+ call_unaffected_off: {
+ code: `
+ ${generateDiagnostic('directive', 'off', 'derivative_uniformity')};
+ fn foo() { _ = textureSample(t,s,0.0); }
+ ${scopeCode(`
+ ${generateDiagnostic('', 'error', 'derivative_uniformity')}
+ if non_uniform_cond {
+ foo();
+ }`)}
+ `,
+ result: true,
+ },
+ if_condition_error: {
+ code: scopeCode(`
+ if (non_uniform_cond) {
+ ${generateDiagnostic('', 'error', 'derivative_uniformity')}
+ if textureSample(t,s,non_uniform_coord).x > 0.0
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')} {
+ }
+ }`),
+ result: false,
+ },
+ if_condition_warn: {
+ code: scopeCode(`
+ if non_uniform_cond {
+ ${generateDiagnostic('', 'warning', 'derivative_uniformity')}
+ if textureSample(t,s,non_uniform_coord).x > 0.0
+ ${generateDiagnostic('', 'error', 'derivative_uniformity')} {
+ }
+ }`),
+ result: 'warn',
+ },
+ if_condition_off: {
+ code: scopeCode(`
+ if non_uniform_cond {
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')}
+ if textureSample(t,s,non_uniform_coord).x > 0.0
+ ${generateDiagnostic('', 'error', 'derivative_uniformity')} {
+ }
+ }`),
+ result: true,
+ },
+ switch_error: {
+ code: scopeCode(`
+ ${generateDiagnostic('', 'error', 'derivative_uniformity')}
+ switch non_uniform_val {
+ case 0 ${generateDiagnostic('', 'off', 'derivative_uniformity')} {
+ }
+ default {
+ _ = textureSample(t,s,0.0);
+ }
+ }`),
+ result: false,
+ },
+ switch_warn: {
+ code: scopeCode(`
+ ${generateDiagnostic('', 'warning', 'derivative_uniformity')}
+ switch non_uniform_val {
+ case 0 ${generateDiagnostic('', 'off', 'derivative_uniformity')} {
+ }
+ default {
+ _ = textureSample(t,s,0.0);
+ }
+ }`),
+ result: 'warn',
+ },
+ switch_off: {
+ code: scopeCode(`
+ ${generateDiagnostic('', 'off', 'derivative_uniformity')}
+ switch non_uniform_val {
+ case 0 ${generateDiagnostic('', 'error', 'derivative_uniformity')}{
+ }
+ default {
+ _ = textureSample(t,s,0.0);
+ }
+ }`),
+ result: true,
+ },
+};
+
+g.test('diagnostic_scoping')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#diagnostics')
+ .desc('Tests that innermost scope controls the diagnostic')
+ .params(u => u.combine('case', keysOf(kScopeCases)))
+ .fn(t => {
+ const testcase = kScopeCases[t.params.case];
+ if (testcase.result === 'warn') {
+ t.expectCompileWarning(true, testcase.code);
+ } else {
+ t.expectCompileResult(testcase.result as boolean, testcase.code);
+ }
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/enable.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/enable.spec.ts
index 230244c6b8..799053b547 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/enable.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/enable.spec.ts
@@ -13,11 +13,21 @@ const kCases = {
enable f16;`,
pass: false,
},
- after_decl: {
+ decl_after: {
code: `enable f16;
alias i = i32;`,
pass: true,
},
+ requires_before: {
+ code: `requires readonly_and_readwrite_storage_textures;
+enable f16;`,
+ pass: true,
+ },
+ diagnostic_before: {
+ code: `diagnostic(info, derivative_uniformity);
+enable f16;`,
+ pass: true,
+ },
const_assert_before: {
code: `const_assert 1 == 1;
enable f16;`,
@@ -48,7 +58,7 @@ f16;`,
enable f16;`,
pass: true,
},
- multipe_entries: {
+ multiple_entries: {
code: `enable f16, f16, f16;`,
pass: true,
},
@@ -65,6 +75,10 @@ g.test('enable')
})
.params(u => u.combine('case', keysOf(kCases)))
.fn(t => {
+ if (t.params.case === 'requires_before') {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+ }
+
const c = kCases[t.params.case];
t.expectCompileResult(c.pass, c.code);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/must_use.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/must_use.spec.ts
index dd36fabcf6..058a5f8c9b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/must_use.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/must_use.spec.ts
@@ -57,27 +57,74 @@ g.test('declaration')
});
const kMustUseCalls = {
+ no_call: ``, // Never calling a @must_use function should pass
phony: `_ = bar();`,
let: `let tmp = bar();`,
- var: `var tmp = bar();`,
+ local_var: `var tmp = bar();`,
+ private_var: `private_var = bar();`,
+ storage_var: `storage_var = bar();`,
+ pointer: `
+ var a : f32;
+ let p = &a;
+ (*p) = bar();`,
+ vector_elem: `
+ var a : vec3<f32>;
+ a.x = bar();`,
+ matrix_elem: `
+ var a : mat3x2<f32>;
+ a[0][0] = bar();`,
condition: `if bar() == 0 { }`,
param: `baz(bar());`,
- statement: `bar();`,
+ return: `return bar();`,
+ statement: `bar();`, // should fail if bar is @must_use
};
g.test('call')
.desc(`Validate that a call to must_use function cannot be the whole function call statement`)
- .params(u => u.combine('use', ['@must_use', ''] as const).combine('call', keysOf(kMustUseCalls)))
+ .params(u =>
+ u //
+ .combine('use', ['@must_use', ''] as const)
+ .combine('call', keysOf(kMustUseCalls))
+ )
.fn(t => {
const test = kMustUseCalls[t.params.call];
const code = `
- fn baz(param : u32) { }
- ${t.params.use} fn bar() -> u32 { return 0; }
- fn foo() {
+ @group(0) @binding(0) var<storage, read_write> storage_var : f32;
+ var<private> private_var : f32;
+
+ fn baz(param : f32) { }
+
+ ${t.params.use} fn bar() -> f32 { return 0; }
+
+ fn foo() ${t.params.call === 'return' ? '-> f32' : ''} {
${test}
}`;
- const res = t.params.call !== 'statement' || t.params.use === '';
- t.expectCompileResult(res, code);
+
+ const should_pass = t.params.call !== 'statement' || t.params.use === '';
+ t.expectCompileResult(should_pass, code);
+ });
+
+g.test('ignore_result_of_non_must_use_that_returns_call_of_must_use')
+ .desc(
+ `Test that ignoring the result of a non-@must_use function that returns the result of a @must_use function succeeds`
+ )
+ .fn(t => {
+ const wgsl = `
+ @must_use
+ fn f() -> f32 {
+ return 0;
+ }
+
+ fn g() -> f32 {
+ return f();
+ }
+
+ fn main() {
+ g(); // Ignore result
+ }
+ `;
+
+ t.expectCompileResult(true, wgsl);
});
const kMustUseBuiltinCalls = {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/pipeline_stage.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/pipeline_stage.spec.ts
index 78dcb95782..f492121f25 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/pipeline_stage.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/pipeline_stage.spec.ts
@@ -73,34 +73,46 @@ g.test('multiple_entry_points')
t.expectCompileResult(true, code);
});
-g.test('duplicate_compute_on_function')
- .desc(`Test that duplcate @compute attributes are not allowed.`)
- .params(u => u.combine('dupe', ['', '@compute']))
+g.test('extra_on_compute_function')
+ .desc(`Test that an extra stage attribute on @compute functions are not allowed.`)
+ .params(u =>
+ u.combine('extra', ['', '@compute', '@fragment', '@vertex']).combine('before', [false, true])
+ )
.fn(t => {
+ const before = t.params.before ? t.params.extra : '';
+ const after = t.params.before ? '' : t.params.extra;
const code = `
-@compute ${t.params.dupe} @workgroup_size(1) fn compute_1() {}
+${before} @compute ${after} @workgroup_size(1) fn main() {}
`;
- t.expectCompileResult(t.params.dupe === '', code);
+ t.expectCompileResult(t.params.extra === '', code);
});
-g.test('duplicate_fragment_on_function')
- .desc(`Test that duplcate @fragment attributes are not allowed.`)
- .params(u => u.combine('dupe', ['', '@fragment']))
+g.test('extra_on_fragment_function')
+ .desc(`Test that an extra stage attribute on @fragment functions are not allowed.`)
+ .params(u =>
+ u.combine('extra', ['', '@compute', '@fragment', '@vertex']).combine('before', [false, true])
+ )
.fn(t => {
+ const before = t.params.before ? t.params.extra : '';
+ const after = t.params.before ? '' : t.params.extra;
const code = `
-@fragment ${t.params.dupe} fn vtx() -> @location(0) vec4f { return vec4f(1); }
+${before} @fragment ${after} fn main() -> @location(0) vec4f { return vec4f(1); }
`;
- t.expectCompileResult(t.params.dupe === '', code);
+ t.expectCompileResult(t.params.extra === '', code);
});
-g.test('duplicate_vertex_on_function')
- .desc(`Test that duplcate @vertex attributes are not allowed.`)
- .params(u => u.combine('dupe', ['', '@vertex']))
+g.test('extra_on_vertex_function')
+ .desc(`Test that an extra stage attribute on @vertex functions are not allowed.`)
+ .params(u =>
+ u.combine('extra', ['', '@compute', '@fragment', '@vertex']).combine('before', [false, true])
+ )
.fn(t => {
+ const before = t.params.before ? t.params.extra : '';
+ const after = t.params.before ? '' : t.params.extra;
const code = `
-@vertex ${t.params.dupe} fn vtx() -> @builtin(position) vec4f { return vec4f(1); }
+${before} @vertex ${after} fn main() -> @builtin(position) vec4f { return vec4f(1); }
`;
- t.expectCompileResult(t.params.dupe === '', code);
+ t.expectCompileResult(t.params.extra === '', code);
});
g.test('placement')
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/requires.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/requires.spec.ts
new file mode 100644
index 0000000000..2b365ae61e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/requires.spec.ts
@@ -0,0 +1,103 @@
+export const description = `Parser validation tests for requires`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { kKnownWGSLLanguageFeatures } from '../../../capability_info.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kCases = {
+ valid: { code: `requires readonly_and_readwrite_storage_textures;`, pass: true },
+ decl_before: {
+ code: `alias i = i32;
+requires readonly_and_readwrite_storage_textures;`,
+ pass: false,
+ },
+ decl_after: {
+ code: `requires readonly_and_readwrite_storage_textures;
+alias i = i32;`,
+ pass: true,
+ },
+ enable_before: {
+ code: `enable f16;
+requires readonly_and_readwrite_storage_textures;`,
+ pass: true,
+ },
+ diagnostic_before: {
+ code: `diagnostic(info, derivative_uniformity);
+requires readonly_and_readwrite_storage_textures;`,
+ pass: true,
+ },
+ const_assert_before: {
+ code: `const_assert 1 == 1;
+requires readonly_and_readwrite_storage_textures;`,
+ pass: false,
+ },
+ const_assert_after: {
+ code: `requires readonly_and_readwrite_storage_textures;
+const_assert 1 == 1;`,
+ pass: true,
+ },
+ embedded_comment: {
+ code: `/* comment
+
+*/requires readonly_and_readwrite_storage_textures;`,
+ pass: true,
+ },
+ parens: {
+ code: `requires(readonly_and_readwrite_storage_textures);`,
+ pass: false,
+ },
+ multi_line: {
+ code: `requires
+readonly_and_readwrite_storage_textures;`,
+ pass: true,
+ },
+ multiple_requires_duplicate: {
+ code: `requires readonly_and_readwrite_storage_textures;
+requires readonly_and_readwrite_storage_textures;`,
+ pass: true,
+ },
+ multiple_requires_different: {
+ code: `requires readonly_and_readwrite_storage_textures;
+requires packed_4x8_integer_dot_product;`,
+ pass: true,
+ },
+ multiple_entries_duplicate: {
+ code: `requires readonly_and_readwrite_storage_textures, readonly_and_readwrite_storage_textures, readonly_and_readwrite_storage_textures;`,
+ pass: true,
+ },
+ multiple_entries_different: {
+ code: `requires readonly_and_readwrite_storage_textures, packed_4x8_integer_dot_product;`,
+ pass: true,
+ },
+ unknown: {
+ code: `requires unknown;`,
+ pass: false,
+ },
+};
+
+g.test('requires')
+ .desc(`Tests that requires are validated correctly.`)
+ .params(u => u.combine('case', keysOf(kCases)))
+ .beforeAllSubcases(t => {
+ if (t.params.case === 'enable_before') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+ t.skipIfLanguageFeatureNotSupported('packed_4x8_integer_dot_product');
+
+ const c = kCases[t.params.case];
+ t.expectCompileResult(c.pass, c.code);
+ });
+
+g.test('wgsl_matches_api')
+ .desc(`Tests that language features are accepted iff the API reports support for them.`)
+ .params(u => u.combine('feature', kKnownWGSLLanguageFeatures))
+ .fn(t => {
+ const code = `requires ${t.params.feature};`;
+ t.expectCompileResult(t.hasLanguageFeature(t.params.feature), code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/semicolon.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/semicolon.spec.ts
index 87cffcfafc..15a225e19f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/semicolon.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/semicolon.spec.ts
@@ -27,6 +27,21 @@ g.test('after_enable')
t.expectCompileResult(/* pass */ false, `enable f16`);
});
+g.test('after_requires')
+ .desc(`Test that a semicolon must be placed after a requires directive.`)
+ .fn(t => {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+ t.expectCompileResult(/* pass */ true, `requires readonly_and_readwrite_storage_textures;`);
+ t.expectCompileResult(/* pass */ false, `requires readonly_and_readwrite_storage_textures`);
+ });
+
+g.test('after_diagnostic')
+ .desc(`Test that a semicolon must be placed after a requires directive.`)
+ .fn(t => {
+ t.expectCompileResult(/* pass */ true, `diagnostic(info, derivative_uniformity);`);
+ t.expectCompileResult(/* pass */ false, `diagnostic(info, derivative_uniformity)`);
+ });
+
g.test('after_struct_decl')
.desc(`Test that a semicolon can be placed after an struct declaration.`)
.fn(t => {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/shadow_builtins.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/shadow_builtins.spec.ts
new file mode 100644
index 0000000000..3f72a0bf72
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/shadow_builtins.spec.ts
@@ -0,0 +1,995 @@
+export const description = `Validation tests for identifiers`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('function_param')
+ .desc(
+ `Test that a function param can shadow a builtin, but the builtin is available for other params.`
+ )
+ .fn(t => {
+ const code = `
+fn f(f: i32, i32: i32, t: i32) -> i32 { return i32; }
+ `;
+ t.expectCompileResult(true, code);
+ });
+
+const kTests = {
+ abs: {
+ keyword: `abs`,
+ src: `_ = abs(1);`,
+ },
+ acos: {
+ keyword: `acos`,
+ src: `_ = acos(.2);`,
+ },
+ acosh: {
+ keyword: `acosh`,
+ src: `_ = acosh(1.2);`,
+ },
+ all: {
+ keyword: `all`,
+ src: `_ = all(true);`,
+ },
+ any: {
+ keyword: `any`,
+ src: `_ = any(true);`,
+ },
+ array_templated: {
+ keyword: `array`,
+ src: `_ = array<i32, 2>(1, 2);`,
+ },
+ array: {
+ keyword: `array`,
+ src: `_ = array(1, 2);`,
+ },
+ array_length: {
+ keyword: `arrayLength`,
+ src: `_ = arrayLength(&placeholder.rt_arr);`,
+ },
+ asin: {
+ keyword: `asin`,
+ src: `_ = asin(.2);`,
+ },
+ asinh: {
+ keyword: `asinh`,
+ src: `_ = asinh(1.2);`,
+ },
+ atan: {
+ keyword: `atan`,
+ src: `_ = atan(1.2);`,
+ },
+ atanh: {
+ keyword: `atanh`,
+ src: `_ = atanh(.2);`,
+ },
+ atan2: {
+ keyword: `atan2`,
+ src: `_ = atan2(1.2, 2.3);`,
+ },
+ bool: {
+ keyword: `bool`,
+ src: `_ = bool(1);`,
+ },
+ bitcast: {
+ keyword: `bitcast`,
+ src: `_ = bitcast<f32>(1i);`,
+ },
+ ceil: {
+ keyword: `ceil`,
+ src: `_ = ceil(1.23);`,
+ },
+ clamp: {
+ keyword: `clamp`,
+ src: `_ = clamp(1, 2, 3);`,
+ },
+ cos: {
+ keyword: `cos`,
+ src: `_ = cos(2);`,
+ },
+ cosh: {
+ keyword: `cosh`,
+ src: `_ = cosh(2.2);`,
+ },
+ countLeadingZeros: {
+ keyword: `countLeadingZeros`,
+ src: `_ = countLeadingZeros(1);`,
+ },
+ countOneBits: {
+ keyword: `countOneBits`,
+ src: `_ = countOneBits(1);`,
+ },
+ countTrailingZeros: {
+ keyword: `countTrailingZeros`,
+ src: `_ = countTrailingZeros(1);`,
+ },
+ cross: {
+ keyword: `cross`,
+ src: `_ = cross(vec3(1, 2, 3), vec3(4, 5, 6));`,
+ },
+ degrees: {
+ keyword: `degrees`,
+ src: `_ = degrees(1);`,
+ },
+ determinant: {
+ keyword: `determinant`,
+ src: `_ = determinant(mat2x2(1, 2, 3, 4));`,
+ },
+ distance: {
+ keyword: `distance`,
+ src: `_ = distance(1, 2);`,
+ },
+ dot: {
+ keyword: `dot`,
+ src: `_ = dot(vec2(1, 2,), vec2(2, 3));`,
+ },
+ dot4U8Packed: {
+ keyword: `dot4U8Packed`,
+ src: `_ = dot4U8Packed(1, 2);`,
+ },
+ dot4I8Packed: {
+ keyword: `dot4I8Packed`,
+ src: `_ = dot4I8Packed(1, 2);`,
+ },
+ dpdx: {
+ keyword: `dpdx`,
+ src: `_ = dpdx(2);`,
+ },
+ dpdxCoarse: {
+ keyword: `dpdxCoarse`,
+ src: `_ = dpdxCoarse(2);`,
+ },
+ dpdxFine: {
+ keyword: `dpdxFine`,
+ src: `_ = dpdxFine(2);`,
+ },
+ dpdy: {
+ keyword: `dpdy`,
+ src: `_ = dpdy(2);`,
+ },
+ dpdyCoarse: {
+ keyword: `dpdyCoarse`,
+ src: `_ = dpdyCoarse(2);`,
+ },
+ dpdyFine: {
+ keyword: `dpdyFine`,
+ src: `_ = dpdyFine(2);`,
+ },
+ exp: {
+ keyword: `exp`,
+ src: `_ = exp(1);`,
+ },
+ exp2: {
+ keyword: `exp2`,
+ src: `_ = exp2(2);`,
+ },
+ extractBits: {
+ keyword: `extractBits`,
+ src: `_ = extractBits(1, 2, 3);`,
+ },
+ f32: {
+ keyword: `f32`,
+ src: `_ = f32(1i);`,
+ },
+ faceForward: {
+ keyword: `faceForward`,
+ src: `_ = faceForward(vec2(1, 2), vec2(3, 4), vec2(5, 6));`,
+ },
+ firstLeadingBit: {
+ keyword: `firstLeadingBit`,
+ src: `_ = firstLeadingBit(1);`,
+ },
+ firstTrailingBit: {
+ keyword: `firstTrailingBit`,
+ src: `_ = firstTrailingBit(1);`,
+ },
+ floor: {
+ keyword: `floor`,
+ src: `_ = floor(1.2);`,
+ },
+ fma: {
+ keyword: `fma`,
+ src: `_ = fma(1, 2, 3);`,
+ },
+ fract: {
+ keyword: `fract`,
+ src: `_ = fract(1);`,
+ },
+ frexp: {
+ keyword: `frexp`,
+ src: `_ = frexp(1);`,
+ },
+ fwidth: {
+ keyword: `fwidth`,
+ src: `_ = fwidth(2);`,
+ },
+ fwidthCoarse: {
+ keyword: `fwidthCoarse`,
+ src: `_ = fwidthCoarse(2);`,
+ },
+ fwidthFine: {
+ keyword: `fwidthFine`,
+ src: `_ = fwidthFine(2);`,
+ },
+ i32: {
+ keyword: `i32`,
+ src: `_ = i32(2u);`,
+ },
+ insertBits: {
+ keyword: `insertBits`,
+ src: `_ = insertBits(1, 2, 3, 4);`,
+ },
+ inverseSqrt: {
+ keyword: `inverseSqrt`,
+ src: `_ = inverseSqrt(1);`,
+ },
+ ldexp: {
+ keyword: `ldexp`,
+ src: `_ = ldexp(1, 2);`,
+ },
+ length: {
+ keyword: `length`,
+ src: `_ = length(1);`,
+ },
+ log: {
+ keyword: `log`,
+ src: `_ = log(2);`,
+ },
+ log2: {
+ keyword: `log2`,
+ src: `_ = log2(2);`,
+ },
+ mat2x2_templated: {
+ keyword: `mat2x2`,
+ src: `_ = mat2x2<f32>(1, 2, 3, 4);`,
+ },
+ mat2x2: {
+ keyword: `mat2x2`,
+ src: `_ = mat2x2(1, 2, 3, 4);`,
+ },
+ mat2x3_templated: {
+ keyword: `mat2x3`,
+ src: `_ = mat2x3<f32>(1, 2, 3, 4, 5, 6);`,
+ },
+ mat2x3: {
+ keyword: `mat2x3`,
+ src: `_ = mat2x3(1, 2, 3, 4, 5, 6);`,
+ },
+ mat2x4_templated: {
+ keyword: `mat2x4`,
+ src: `_ = mat2x4<f32>(1, 2, 3, 4, 5, 6, 7, 8);`,
+ },
+ mat2x4: {
+ keyword: `mat2x4`,
+ src: `_ = mat2x4(1, 2, 3, 4, 5, 6, 7, 8);`,
+ },
+ mat3x2_templated: {
+ keyword: `mat3x2`,
+ src: `_ = mat3x2<f32>(1, 2, 3, 4, 5, 6);`,
+ },
+ mat3x2: {
+ keyword: `mat3x2`,
+ src: `_ = mat3x2(1, 2, 3, 4, 5, 6);`,
+ },
+ mat3x3_templated: {
+ keyword: `mat3x3`,
+ src: `_ = mat3x3<f32>(1, 2, 3, 4, 5, 6, 7, 8, 9);`,
+ },
+ mat3x3: {
+ keyword: `mat3x3`,
+ src: `_ = mat3x3(1, 2, 3, 4, 5, 6, 7, 8, 9);`,
+ },
+ mat3x4_templated: {
+ keyword: `mat3x4`,
+ src: `_ = mat3x4<f32>(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);`,
+ },
+ mat3x4: {
+ keyword: `mat3x4`,
+ src: `_ = mat3x4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);`,
+ },
+ mat4x2_templated: {
+ keyword: `mat4x2`,
+ src: `_ = mat4x2<f32>(1, 2, 3, 4, 5, 6, 7, 8);`,
+ },
+ mat4x2: {
+ keyword: `mat4x2`,
+ src: `_ = mat4x2(1, 2, 3, 4, 5, 6, 7, 8);`,
+ },
+ mat4x3_templated: {
+ keyword: `mat4x3`,
+ src: `_ = mat4x3<f32>(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);`,
+ },
+ mat4x3: {
+ keyword: `mat4x3`,
+ src: `_ = mat4x3(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);`,
+ },
+ mat4x4_templated: {
+ keyword: `mat4x4`,
+ src: `_ = mat4x4<f32>(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);`,
+ },
+ mat4x4: {
+ keyword: `mat4x4`,
+ src: `_ = mat4x4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);`,
+ },
+ max: {
+ keyword: `max`,
+ src: `_ = max(1, 2);`,
+ },
+ min: {
+ keyword: `min`,
+ src: `_ = min(1, 2);`,
+ },
+ mix: {
+ keyword: `mix`,
+ src: `_ = mix(1, 2, 3);`,
+ },
+ modf: {
+ keyword: `modf`,
+ src: `_ = modf(1.2);`,
+ },
+ normalize: {
+ keyword: `normalize`,
+ src: `_ = normalize(vec2(1, 2));`,
+ },
+ pack2x16snorm: {
+ keyword: `pack2x16snorm`,
+ src: `_ = pack2x16snorm(vec2(1, 2));`,
+ },
+ pack2x16unorm: {
+ keyword: `pack2x16unorm`,
+ src: `_ = pack2x16unorm(vec2(1, 2));`,
+ },
+ pack2x16float: {
+ keyword: `pack2x16float`,
+ src: `_ = pack2x16float(vec2(1, 2));`,
+ },
+ pack4x8snorm: {
+ keyword: `pack4x8snorm`,
+ src: `_ = pack4x8snorm(vec4(1, 2, 3, 4));`,
+ },
+ pack4x8unorm: {
+ keyword: `pack4x8unorm`,
+ src: `_ = pack4x8unorm(vec4(1, 2, 3, 4));`,
+ },
+ pack4xI8: {
+ keyword: `pack4xI8`,
+ src: `_ = pack4xI8(vec4(1, 2, 3, 4));`,
+ },
+ pack4xU8: {
+ keyword: `pack4xU8`,
+ src: `_ = pack4xU8(vec4(1, 2, 3, 4));`,
+ },
+ pack4xI8Clamp: {
+ keyword: `pack4xI8Clamp`,
+ src: `_ = pack4xI8Clamp(vec4(1, 2, 3, 4));`,
+ },
+ pack4xU8Clamp: {
+ keyword: `pack4xU8Clamp`,
+ src: `_ = pack4xU8Clamp(vec4(1, 2, 3, 4));`,
+ },
+ pow: {
+ keyword: `pow`,
+ src: `_ = pow(1, 2);`,
+ },
+ quantizeToF16: {
+ keyword: `quantizeToF16`,
+ src: `_ = quantizeToF16(1.2);`,
+ },
+ radians: {
+ keyword: `radians`,
+ src: `_ = radians(1.2);`,
+ },
+ reflect: {
+ keyword: `reflect`,
+ src: `_ = reflect(vec2(1, 2), vec2(3, 4));`,
+ },
+ refract: {
+ keyword: `refract`,
+ src: `_ = refract(vec2(1, 1), vec2(2, 2), 3);`,
+ },
+ reverseBits: {
+ keyword: `reverseBits`,
+ src: `_ = reverseBits(1);`,
+ },
+ round: {
+ keyword: `round`,
+ src: `_ = round(1.2);`,
+ },
+ saturate: {
+ keyword: `saturate`,
+ src: `_ = saturate(1);`,
+ },
+ select: {
+ keyword: `select`,
+ src: `_ = select(1, 2, false);`,
+ },
+ sign: {
+ keyword: `sign`,
+ src: `_ = sign(1);`,
+ },
+ sin: {
+ keyword: `sin`,
+ src: `_ = sin(2);`,
+ },
+ sinh: {
+ keyword: `sinh`,
+ src: `_ = sinh(3);`,
+ },
+ smoothstep: {
+ keyword: `smoothstep`,
+ src: `_ = smoothstep(1, 2, 3);`,
+ },
+ sqrt: {
+ keyword: `sqrt`,
+ src: `_ = sqrt(24);`,
+ },
+ step: {
+ keyword: `step`,
+ src: `_ = step(4, 5);`,
+ },
+ tan: {
+ keyword: `tan`,
+ src: `_ = tan(2);`,
+ },
+ tanh: {
+ keyword: `tanh`,
+ src: `_ = tanh(2);`,
+ },
+ transpose: {
+ keyword: `transpose`,
+ src: `_ = transpose(mat2x2(1, 2, 3, 4));`,
+ },
+ trunc: {
+ keyword: `trunc`,
+ src: `_ = trunc(2);`,
+ },
+ u32: {
+ keyword: `u32`,
+ src: `_ = u32(1i);`,
+ },
+ unpack2x16snorm: {
+ keyword: `unpack2x16snorm`,
+ src: `_ = unpack2x16snorm(2);`,
+ },
+ unpack2x16unorm: {
+ keyword: `unpack2x16unorm`,
+ src: `_ = unpack2x16unorm(2);`,
+ },
+ unpack2x16float: {
+ keyword: `unpack2x16float`,
+ src: `_ = unpack2x16float(2);`,
+ },
+ unpack4x8snorm: {
+ keyword: `unpack4x8snorm`,
+ src: `_ = unpack4x8snorm(4);`,
+ },
+ unpack4x8unorm: {
+ keyword: `unpack4x8unorm`,
+ src: `_ = unpack4x8unorm(4);`,
+ },
+ unpack4xI8: {
+ keyword: `unpack4xI8`,
+ src: `_ = unpack4xI8(4);`,
+ },
+ unpack4xU8: {
+ keyword: `unpack4xU8`,
+ src: `_ = unpack4xU8(4);`,
+ },
+ vec2_templated: {
+ keyword: `vec2`,
+ src: `_ = vec2<f32>(1, 2);`,
+ },
+ vec2: {
+ keyword: `vec2`,
+ src: `_ = vec2(1, 2);`,
+ },
+ vec3_templated: {
+ keyword: `vec3`,
+ src: `_ = vec3<f32>(1, 2, 3);`,
+ },
+ vec3: {
+ keyword: `vec3`,
+ src: `_ = vec3(1, 2, 3);`,
+ },
+ vec4_templated: {
+ keyword: `vec4`,
+ src: `_ = vec4<f32>(1, 2, 3, 4);`,
+ },
+ vec4: {
+ keyword: `vec4`,
+ src: `_ = vec4(1, 2, 3, 4);`,
+ },
+};
+
+g.test('shadow_hides_builtin')
+ .desc(`Test that shadows hide builtins.`)
+ .params(u =>
+ u
+ .combine('inject', ['none', 'function', 'sibling', 'module'] as const)
+ .beginSubcases()
+ .combine('builtin', keysOf(kTests))
+ )
+ .fn(t => {
+ const data = kTests[t.params.builtin];
+ const local = `let ${data.keyword} = 4;`;
+
+ const module_shadow = t.params.inject === 'module' ? `var<private> ${data.keyword} : i32;` : ``;
+ const sibling_func = t.params.inject === 'sibling' ? local : ``;
+ const func = t.params.inject === 'function' ? local : ``;
+
+ const code = `
+struct Data {
+ rt_arr: array<i32>,
+}
+@group(0) @binding(0) var<storage> placeholder: Data;
+
+${module_shadow}
+
+fn sibling() {
+ ${sibling_func}
+}
+
+@fragment
+fn main() -> @location(0) vec4f {
+ ${func}
+ ${data.src}
+ return vec4f(1);
+}
+ `;
+
+ const pass = t.params.inject === 'none' || t.params.inject === 'sibling';
+ t.expectCompileResult(pass, code);
+ });
+
+const kFloat16Tests = {
+ f16: {
+ keyword: `f16`,
+ src: `_ = f16(2);`,
+ },
+};
+
+g.test('shadow_hides_builtin_f16')
+ .desc(`Test that shadows hide builtins when shader-f16 is enabled.`)
+ .params(u =>
+ u
+ .combine('inject', ['none', 'function', 'sibling', 'module'] as const)
+ .beginSubcases()
+ .combine('builtin', keysOf(kFloat16Tests))
+ )
+ .beforeAllSubcases(t => {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ })
+ .fn(t => {
+ const data = kFloat16Tests[t.params.builtin];
+ const local = `let ${data.keyword} = 4;`;
+
+ const module_shadow = t.params.inject === 'module' ? `var<private> ${data.keyword} : f16;` : ``;
+ const sibling_func = t.params.inject === 'sibling' ? local : ``;
+ const func = t.params.inject === 'function' ? local : ``;
+
+ const code = `
+enable f16;
+
+${module_shadow}
+
+fn sibling() {
+ ${sibling_func}
+}
+
+@vertex
+fn vtx() -> @builtin(position) vec4f {
+ ${func}
+ ${data.src}
+ return vec4f(1);
+}
+ `;
+ const pass = t.params.inject === 'none' || t.params.inject === 'sibling';
+ t.expectCompileResult(pass, code);
+ });
+
+const kTextureTypeTests = {
+ texture_1d: {
+ keyword: `texture_1d`,
+ src: `var t: texture_1d<f32>;`,
+ },
+ texture_2d: {
+ keyword: `texture_2d`,
+ src: `var t: texture_2d<f32>;`,
+ },
+ texture_2d_array: {
+ keyword: `texture_2d_array`,
+ src: `var t: texture_2d_array<f32>;`,
+ },
+ texture_3d: {
+ keyword: `texture_3d`,
+ src: `var t: texture_3d<f32>;`,
+ },
+ texture_cube: {
+ keyword: `texture_cube`,
+ src: `var t: texture_cube<f32>;`,
+ },
+ texture_cube_array: {
+ keyword: `texture_cube_array`,
+ src: `var t: texture_cube_array<f32>;`,
+ },
+ texture_multisampled_2d: {
+ keyword: `texture_multisampled_2d`,
+ src: `var t: texture_multisampled_2d<f32>;`,
+ },
+ texture_depth_multisampled_2d: {
+ keyword: `texture_depth_multisampled_2d`,
+ src: `var t: texture_depth_multisampled_2d;`,
+ },
+ texture_external: {
+ keyword: `texture_external`,
+ src: `var t: texture_external;`,
+ },
+ texture_storage_1d: {
+ keyword: `texture_storage_1d`,
+ src: `var t: texture_storage_1d<rgba8unorm, read_write>;`,
+ },
+ texture_storage_2d: {
+ keyword: `texture_storage_2d`,
+ src: `var t: texture_storage_2d<rgba8unorm, read_write>;`,
+ },
+ texture_storage_2d_array: {
+ keyword: `texture_storage_2d_array`,
+ src: `var t: texture_storage_2d_array<rgba8unorm, read_write>;`,
+ },
+ texture_storage_3d: {
+ keyword: `texture_storage_3d`,
+ src: `var t: texture_storage_3d<rgba8unorm, read_write>;`,
+ },
+ texture_depth_2d: {
+ keyword: `texture_depth_2d`,
+ src: `var t: texture_depth_2d;`,
+ },
+ texture_depth_2d_array: {
+ keyword: `texture_depth_2d_array`,
+ src: `var t: texture_depth_2d_array;`,
+ },
+ texture_depth_cube: {
+ keyword: `texture_depth_cube`,
+ src: `var t: texture_depth_cube;`,
+ },
+ texture_depth_cube_array: {
+ keyword: `texture_depth_cube_array`,
+ src: `var t: texture_depth_cube_array;`,
+ },
+ sampler: {
+ keyword: `sampler`,
+ src: `var s: sampler;`,
+ },
+ sampler_comparison: {
+ keyword: `sampler_comparison`,
+ src: `var s: sampler_comparison;`,
+ },
+};
+
+g.test('shadow_hides_builtin_handle_type')
+ .desc(`Test that shadows hide builtins when handle address space types are used.`)
+ .params(u =>
+ u
+ .combine('inject', ['none', 'function', 'module'] as const)
+ .beginSubcases()
+ .combine('builtin', keysOf(kTextureTypeTests))
+ )
+ .fn(t => {
+ const data = kTextureTypeTests[t.params.builtin];
+ const local = `let ${data.keyword} = 4;`;
+
+ const module_shadow = t.params.inject === 'module' ? `var<private> ${data.keyword} : f32;` : ``;
+ const func = t.params.inject === 'function' ? local : ``;
+
+ const code = `
+${module_shadow}
+@group(0) @binding(0) ${data.src}
+
+fn func() {
+ ${func}
+}
+ `;
+ const pass = t.params.inject === 'none' || t.params.inject === 'function';
+ t.expectCompileResult(pass, code);
+ });
+
+const kTextureTests = {
+ textureDimensions: {
+ keyword: `textureDimensions`,
+ src: `_ = textureDimensions(t_2d);`,
+ },
+ textureGather: {
+ keyword: `textureGather`,
+ src: `_ = textureGather(1, t_2d, s, vec2(1, 2));`,
+ },
+ textureGatherCompare: {
+ keyword: `textureGatherCompare`,
+ src: `_ = textureGatherCompare(t_2d_depth, sc, vec2(1, 2), 3);`,
+ },
+ textureLoad: {
+ keyword: `textureLoad`,
+ src: `_ = textureLoad(t_2d, vec2(1, 2), 1);`,
+ },
+ textureNumLayers: {
+ keyword: `textureNumLayers`,
+ src: `_ = textureNumLayers(t_2d_array);`,
+ },
+ textureNumLevels: {
+ keyword: `textureNumLevels`,
+ src: `_ = textureNumLevels(t_2d);`,
+ },
+ textureNumSamples: {
+ keyword: `textureNumSamples`,
+ src: `_ = textureNumSamples(t_2d_ms);`,
+ },
+ textureSample: {
+ keyword: `textureSample`,
+ src: `_ = textureSample(t_2d, s, vec2(1, 2));`,
+ },
+ textureSampleBias: {
+ keyword: `textureSampleBias`,
+ src: `_ = textureSampleBias(t_2d, s, vec2(1, 2), 2);`,
+ },
+ textureSampleCompare: {
+ keyword: `textureSampleCompare`,
+ src: `_ = textureSampleCompare(t_2d_depth, sc, vec2(1, 2), 2);`,
+ },
+ textureSampleCompareLevel: {
+ keyword: `textureSampleCompareLevel`,
+ src: `_ = textureSampleCompareLevel(t_2d_depth, sc, vec2(1, 2), 3, vec2(1, 2));`,
+ },
+ textureSampleGrad: {
+ keyword: `textureSampleGrad`,
+ src: `_ = textureSampleGrad(t_2d, s, vec2(1, 2), vec2(1, 2), vec2(1, 2));`,
+ },
+ textureSampleLevel: {
+ keyword: `textureSampleLevel`,
+ src: `_ = textureSampleLevel(t_2d, s, vec2(1, 2), 3);`,
+ },
+ textureSampleBaseClampToEdge: {
+ keyword: `textureSampleBaseClampToEdge`,
+ src: `_ = textureSampleBaseClampToEdge(t_2d, s, vec2(1, 2));`,
+ },
+};
+
+g.test('shadow_hides_builtin_texture')
+ .desc(`Test that shadows hide texture builtins.`)
+ .params(u =>
+ u
+ .combine('inject', ['none', 'function', 'sibling', 'module'] as const)
+ .beginSubcases()
+ .combine('builtin', keysOf(kTextureTests))
+ )
+ .fn(t => {
+ const data = kTextureTests[t.params.builtin];
+ const local = `let ${data.keyword} = 4;`;
+
+ const module_shadow = t.params.inject === 'module' ? `var<private> ${data.keyword} : i32;` : ``;
+ const sibling_func = t.params.inject === 'sibling' ? local : ``;
+ const func = t.params.inject === 'function' ? local : ``;
+
+ const code = `
+@group(0) @binding(0) var t_2d: texture_2d<f32>;
+@group(0) @binding(1) var t_2d_depth: texture_depth_2d;
+@group(0) @binding(2) var t_2d_array: texture_2d_array<f32>;
+@group(0) @binding(3) var t_2d_ms: texture_multisampled_2d<f32>;
+
+@group(1) @binding(0) var s: sampler;
+@group(1) @binding(1) var sc: sampler_comparison;
+
+${module_shadow}
+
+fn sibling() {
+ ${sibling_func}
+}
+
+@fragment
+fn main() -> @location(0) vec4f {
+ ${func}
+ ${data.src}
+ return vec4f(1);
+}
+ `;
+
+ const pass = t.params.inject === 'none' || t.params.inject === 'sibling';
+ t.expectCompileResult(pass, code);
+ });
+
+g.test('shadow_hides_builtin_atomic_type')
+ .desc(`Test that shadows hide builtins when atomic types are used.`)
+ .params(u => u.combine('inject', ['none', 'function', 'module'] as const).beginSubcases())
+ .fn(t => {
+ const local = `let atomic = 4;`;
+ const module_shadow = t.params.inject === 'module' ? `var<private> atomic: i32;` : ``;
+ const func = t.params.inject === 'function' ? local : ``;
+
+ const code = `
+${module_shadow}
+
+var<workgroup> val: atomic<i32>;
+
+fn func() {
+ ${func}
+}
+ `;
+ const pass = t.params.inject === 'none' || t.params.inject === 'function';
+ t.expectCompileResult(pass, code);
+ });
+
+const kAtomicTests = {
+ atomicLoad: {
+ keyword: `atomicLoad`,
+ src: `_ = atomicLoad(&a);`,
+ },
+ atomicStore: {
+ keyword: `atomicStore`,
+ src: `atomicStore(&a, 1);`,
+ },
+ atomicAdd: {
+ keyword: `atomicAdd`,
+ src: `_ = atomicAdd(&a, 1);`,
+ },
+ atomicSub: {
+ keyword: `atomicSub`,
+ src: `_ = atomicSub(&a, 1);`,
+ },
+ atomicMax: {
+ keyword: `atomicMax`,
+ src: `_ = atomicMax(&a, 1);`,
+ },
+ atomicMin: {
+ keyword: `atomicMin`,
+ src: `_ = atomicMin(&a, 1);`,
+ },
+ atomicAnd: {
+ keyword: `atomicAnd`,
+ src: `_ = atomicAnd(&a, 1);`,
+ },
+ atomicOr: {
+ keyword: `atomicOr`,
+ src: `_ = atomicOr(&a, 1);`,
+ },
+ atomicXor: {
+ keyword: `atomicXor`,
+ src: `_ = atomicXor(&a, 1);`,
+ },
+};
+
+g.test('shadow_hides_builtin_atomic')
+ .desc(`Test that shadows hide builtin atomic methods.`)
+ .params(u =>
+ u
+ .combine('inject', ['none', 'function', 'sibling', 'module'] as const)
+ .beginSubcases()
+ .combine('builtin', keysOf(kAtomicTests))
+ )
+ .fn(t => {
+ const data = kAtomicTests[t.params.builtin];
+ const local = `let ${data.keyword} = 4;`;
+
+ const module_shadow = t.params.inject === 'module' ? `var<private> ${data.keyword} : i32;` : ``;
+ const sibling_func = t.params.inject === 'sibling' ? local : ``;
+ const func = t.params.inject === 'function' ? local : ``;
+
+ const code = `
+var<workgroup> a: atomic<i32>;
+
+${module_shadow}
+
+fn sibling() {
+ ${sibling_func}
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ ${func}
+ ${data.src}
+}
+ `;
+
+ const pass = t.params.inject === 'none' || t.params.inject === 'sibling';
+ t.expectCompileResult(pass, code);
+ });
+
+const kBarrierTests = {
+ storageBarrier: {
+ keyword: `storageBarrier`,
+ src: `storageBarrier();`,
+ },
+ textureBarrier: {
+ keyword: `textureBarrier`,
+ src: `textureBarrier();`,
+ },
+ workgroupBarrier: {
+ keyword: `workgroupBarrier`,
+ src: `workgroupBarrier();`,
+ },
+ workgroupUniformLoad: {
+ keyword: `workgroupUniformLoad`,
+ src: `_ = workgroupUniformLoad(&u);`,
+ },
+};
+
+g.test('shadow_hides_builtin_barriers')
+ .desc(`Test that shadows hide builtin barrier methods.`)
+ .params(u =>
+ u
+ .combine('inject', ['none', 'function', 'sibling', 'module'] as const)
+ .beginSubcases()
+ .combine('builtin', keysOf(kBarrierTests))
+ )
+ .fn(t => {
+ const data = kBarrierTests[t.params.builtin];
+ const local = `let ${data.keyword} = 4;`;
+
+ const module_shadow = t.params.inject === 'module' ? `var<private> ${data.keyword} : i32;` : ``;
+ const sibling_func = t.params.inject === 'sibling' ? local : ``;
+ const func = t.params.inject === 'function' ? local : ``;
+
+ const code = `
+var<workgroup> u: u32;
+
+${module_shadow}
+
+fn sibling() {
+ ${sibling_func}
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ ${func}
+ ${data.src}
+}
+ `;
+
+ const pass = t.params.inject === 'none' || t.params.inject === 'sibling';
+ t.expectCompileResult(pass, code);
+ });
+
+const kAccessModeTests = {
+ read: {
+ keyword: `read`,
+ src: `var<storage, read> a: i32;`,
+ },
+ read_write: {
+ keyword: `read_write`,
+ src: `var<storage, read_write> a: i32;`,
+ },
+ write: {
+ keyword: `write`,
+ src: `var t: texture_storage_1d<rgba8unorm, write>;`,
+ },
+};
+
+g.test('shadow_hides_access_mode')
+ .desc(`Test that shadows hide access modes.`)
+ .params(u =>
+ u
+ .combine('inject', ['none', 'function', 'module'] as const)
+ .beginSubcases()
+ .combine('builtin', keysOf(kAccessModeTests))
+ )
+ .fn(t => {
+ const data = kAccessModeTests[t.params.builtin];
+ const local = `let ${data.keyword} = 4;`;
+
+ const module_shadow = t.params.inject === 'module' ? `var<private> ${data.keyword} : i32;` : ``;
+ const func = t.params.inject === 'function' ? local : ``;
+
+ const code = `
+${module_shadow}
+
+@group(0) @binding(0) ${data.src}
+
+@compute @workgroup_size(1)
+fn main() {
+ ${func}
+}
+ `;
+
+ const pass = t.params.inject === 'none' || t.params.inject === 'function';
+ t.expectCompileResult(pass, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/statement_behavior.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/statement_behavior.spec.ts
new file mode 100644
index 0000000000..1acfd01d8b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/parse/statement_behavior.spec.ts
@@ -0,0 +1,143 @@
+export const description = `
+Test statement behavior analysis.
+
+Functions must have a behavior of {Return}, {Next}, or {Return, Next}.
+Functions with a return type must have a behavior of {Return}.
+
+Each statement in the function must be valid according to the table.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kInvalidStatements = {
+ break: `break`,
+ break_if: `break if true`,
+ continue: `continue`,
+ loop1: `loop { }`,
+ loop2: `loop { continuing { } }`,
+ loop3: `loop { continue; continuing { } }`,
+ loop4: `loop { continuing { break; } }`,
+ loop5: `loop { continuing { continue; } }`,
+ loop6: `loop { continuing { return; } }`,
+ loop7: `loop { continue; break; }`,
+ loop8: `loop { continuing { break if true; return; } }`,
+ for1: `for (;;) { }`,
+ for2: `for (var i = 0; ; i++) { }`,
+ for3: `for (;; break) { }`,
+ for4: `for (;; continue ) { }`,
+ for5: `for (;; return ) { }`,
+ for6: `for (;;) { continue; break; }`,
+ // while loops always have break in their behaviors.
+ switch1: `switch (1) { case 1 { } }`,
+ sequence1: `return; loop { }`,
+ compound1: `{ loop { } }`,
+};
+
+g.test('invalid_statements')
+ .desc('Test statements with invalid behaviors')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#behaviors-rules')
+ .params(u => u.combine('body', keysOf(kInvalidStatements)))
+ .fn(t => {
+ const body = kInvalidStatements[t.params.body];
+ const code = `fn foo() {
+ ${body};
+ }`;
+ t.expectCompileResult(false, code);
+ });
+
+const kValidStatements = {
+ empty: ``,
+ const_assert: `const_assert true`,
+ let: `let x = 1`,
+ var1: `var x = 1`,
+ var2: `var x : i32`,
+ assign: `v = 1`,
+ phony_assign: `_ = 1`,
+ compound_assign: `v += 1`,
+ return: `return`,
+ discard: `discard`,
+ function_call1: `bar()`,
+ function_call2: `workgroupBarrier()`,
+
+ if1: `if true { } else { }`,
+ if2: `if true { }`,
+
+ break1: `loop { break; }`,
+ break2: `loop { if false { break; } }`,
+ break_if: `loop { continuing { break if false; } }`,
+
+ continue1: `loop { continue; continuing { break if true; } }`,
+
+ loop1: `loop { break; }`,
+ loop2: `loop { break; continuing { } }`,
+ loop3: `loop { continue; continuing { break if true; } }`,
+ loop4: `loop { break; continue; }`,
+
+ for1: `for (; true; ) { }`,
+ for2: `for (;;) { break; }`,
+ for3: `for (;true;) { continue; }`,
+
+ while1: `while true { }`,
+ while2: `while true { continue; }`,
+ while3: `while true { continue; break; }`,
+
+ switch1: `switch 1 { default { } }`,
+ swtich2: `switch 1 { case 1 { } default { } }`,
+ switch3: `switch 1 { default { break; } }`,
+ switch4: `switch 1 { default { } case 1 { break; } }`,
+
+ sequence1: `return; let x = 1`,
+ sequence2: `if true { } let x = 1`,
+ sequence3: `switch 1 { default { break; return; } }`,
+
+ compound1: `{ }`,
+ compound2: `{ loop { break; } if true { return; } }`,
+};
+
+g.test('valid_statements')
+ .desc('Test statements with valid behaviors')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#behaviors-rules')
+ .params(u => u.combine('body', keysOf(kValidStatements)))
+ .fn(t => {
+ const body = kValidStatements[t.params.body];
+ const code = `
+ var<private> v : i32;
+ fn bar() { }
+ fn foo() {
+ ${body};
+ }`;
+ t.expectCompileResult(true, code);
+ });
+
+const kInvalidFunctions = {
+ next_for_type: `fn foo() -> bool { }`,
+ next_return_for_type: `fn foo() -> bool { if true { return true; } }`,
+};
+
+g.test('invalid_functions')
+ .desc('Test functions with invalid behaviors')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#behaviors-rules')
+ .params(u => u.combine('function', keysOf(kInvalidFunctions)))
+ .fn(t => {
+ const func = kInvalidFunctions[t.params.function];
+ t.expectCompileResult(false, func);
+ });
+
+const kValidFunctions = {
+ empty: `fn foo() { }`,
+ next_return: `fn foo() { if true { return; } }`,
+ no_final_return: `fn foo() -> bool { if true { return true; } else { return false; } }`,
+};
+
+g.test('valid_functions')
+ .desc('Test functions with valid behaviors')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#behaviors-rules')
+ .params(u => u.combine('function', keysOf(kValidFunctions)))
+ .fn(t => {
+ const func = kValidFunctions[t.params.function];
+ t.expectCompileResult(true, func);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/binding.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/binding.spec.ts
index 2462025016..ae1b78d931 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/binding.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/binding.spec.ts
@@ -124,17 +124,3 @@ var<storage> a: i32;
}`;
t.expectCompileResult(false, code);
});
-
-g.test('binding_without_group')
- .desc(`Test validation of binding without group`)
- .fn(t => {
- const code = `
-@binding(1)
-var<storage> a: i32;
-
-@workgroup_size(1, 1, 1)
-@compute fn main() {
- _ = a;
-}`;
- t.expectCompileResult(false, code);
- });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/builtins.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/builtins.spec.ts
index 4b32d05539..d99eb82279 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/builtins.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/builtins.spec.ts
@@ -103,9 +103,9 @@ g.test('type')
.params(u =>
u
.combineWithParams(kBuiltins)
- .combine('use_struct', [true, false] as const)
- .combine('target_type', kTestTypes)
.beginSubcases()
+ .combine('target_type', kTestTypes)
+ .combine('use_struct', [true, false] as const)
)
.fn(t => {
let code = '';
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group.spec.ts
index 4d37c43a99..cdbf64201a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group.spec.ts
@@ -124,17 +124,3 @@ var<storage> a: i32;
}`;
t.expectCompileResult(false, code);
});
-
-g.test('group_without_binding')
- .desc(`Test validation of group without binding`)
- .fn(t => {
- const code = `
-@group(1)
-var<storage> a: i32;
-
-@workgroup_size(1, 1, 1)
-@compute fn main() {
- _ = a;
-}`;
- t.expectCompileResult(false, code);
- });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group_and_binding.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group_and_binding.spec.ts
index 08b4b2738a..5a4168267b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group_and_binding.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/group_and_binding.spec.ts
@@ -95,12 +95,12 @@ g.test('single_entry_point')
.combine('stage', ['vertex', 'fragment', 'compute'] as const)
.combine('a_kind', kResourceKindsA)
.combine('b_kind', kResourceKindsB)
+ .combine('usage', ['direct', 'transitive'] as const)
+ .beginSubcases()
.combine('a_group', [0, 3] as const)
.combine('b_group', [0, 3] as const)
.combine('a_binding', [0, 3] as const)
.combine('b_binding', [0, 3] as const)
- .combine('usage', ['direct', 'transitive'] as const)
- .beginSubcases()
)
.fn(t => {
const resourceA = kResourceEmitters.get(t.params.a_kind) as ResourceDeclarationEmitter;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/layout_constraints.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/layout_constraints.spec.ts
new file mode 100644
index 0000000000..7db1eefbe0
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/layout_constraints.spec.ts
@@ -0,0 +1,543 @@
+export const description = `Validation of address space layout constraints`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+interface LayoutCase {
+ type: string;
+ decls?: string;
+ validity: boolean | 'non-uniform' | 'non-interface' | 'storage' | 'atomic';
+ f16?: boolean;
+}
+
+const kLayoutCases: Record<string, LayoutCase> = {
+ // Scalars
+ u32: {
+ type: 'u32',
+ validity: true,
+ },
+ i32: {
+ type: 'i32',
+ validity: true,
+ },
+ f32: {
+ type: 'f32',
+ validity: true,
+ },
+ f16: {
+ type: 'f16',
+ validity: true,
+ f16: true,
+ },
+ bool: {
+ type: 'bool',
+ validity: 'non-interface',
+ },
+
+ // Vectors
+ vec2u: {
+ type: 'vec2u',
+ validity: true,
+ },
+ vec3u: {
+ type: 'vec3u',
+ validity: true,
+ },
+ vec4u: {
+ type: 'vec4u',
+ validity: true,
+ },
+ vec2i: {
+ type: 'vec2i',
+ validity: true,
+ },
+ vec3i: {
+ type: 'vec3i',
+ validity: true,
+ },
+ vec4i: {
+ type: 'vec4i',
+ validity: true,
+ },
+ vec2f: {
+ type: 'vec2f',
+ validity: true,
+ },
+ vec3f: {
+ type: 'vec3f',
+ validity: true,
+ },
+ vec4f: {
+ type: 'vec4f',
+ validity: true,
+ },
+ vec2h: {
+ type: 'vec2h',
+ validity: true,
+ f16: true,
+ },
+ vec3h: {
+ type: 'vec3h',
+ validity: true,
+ f16: true,
+ },
+ vec4h: {
+ type: 'vec4h',
+ validity: true,
+ f16: true,
+ },
+ vec2b: {
+ type: 'vec2<bool>',
+ validity: 'non-interface',
+ },
+ vec3b: {
+ type: 'vec3<bool>',
+ validity: 'non-interface',
+ },
+ vec4b: {
+ type: 'vec4<bool>',
+ validity: 'non-interface',
+ },
+
+ // Matrices
+ mat2x2f: {
+ type: 'mat2x2f',
+ validity: true,
+ },
+ mat2x3f: {
+ type: 'mat2x3f',
+ validity: true,
+ },
+ mat2x4f: {
+ type: 'mat2x4f',
+ validity: true,
+ },
+ mat3x2f: {
+ type: 'mat3x2f',
+ validity: true,
+ },
+ mat3x3f: {
+ type: 'mat3x3f',
+ validity: true,
+ },
+ mat3x4f: {
+ type: 'mat3x4f',
+ validity: true,
+ },
+ mat4x2f: {
+ type: 'mat4x2f',
+ validity: true,
+ },
+ mat4x3f: {
+ type: 'mat4x3f',
+ validity: true,
+ },
+ mat4x4f: {
+ type: 'mat4x4f',
+ validity: true,
+ },
+ mat2x2h: {
+ type: 'mat2x2h',
+ validity: true,
+ f16: true,
+ },
+ mat2x3h: {
+ type: 'mat2x3h',
+ validity: true,
+ f16: true,
+ },
+ mat2x4h: {
+ type: 'mat2x4h',
+ validity: true,
+ f16: true,
+ },
+ mat3x2h: {
+ type: 'mat3x2h',
+ validity: true,
+ f16: true,
+ },
+ mat3x3h: {
+ type: 'mat3x3h',
+ validity: true,
+ f16: true,
+ },
+ mat3x4h: {
+ type: 'mat3x4h',
+ validity: true,
+ f16: true,
+ },
+ mat4x2h: {
+ type: 'mat4x2h',
+ validity: true,
+ f16: true,
+ },
+ mat4x3h: {
+ type: 'mat4x3h',
+ validity: true,
+ f16: true,
+ },
+ mat4x4h: {
+ type: 'mat4x4h',
+ validity: true,
+ f16: true,
+ },
+
+ // Atomics
+ atomic_u32: {
+ type: 'atomic<u32>',
+ validity: 'atomic',
+ },
+ atomic_i32: {
+ type: 'atomic<i32>',
+ validity: 'atomic',
+ },
+
+ // Sized arrays
+ array_u32: {
+ type: 'array<u32, 16>',
+ validity: 'non-uniform',
+ },
+ array_i32: {
+ type: 'array<i32, 16>',
+ validity: 'non-uniform',
+ },
+ array_f32: {
+ type: 'array<f32, 16>',
+ validity: 'non-uniform',
+ },
+ array_f16: {
+ type: 'array<f16, 16>',
+ validity: 'non-uniform',
+ f16: true,
+ },
+ array_bool: {
+ type: 'array<bool, 16>',
+ validity: 'non-interface',
+ },
+ array_vec2f: {
+ type: 'array<vec2f, 16>',
+ validity: 'non-uniform',
+ },
+ array_vec3f: {
+ type: 'array<vec3f, 16>',
+ validity: true,
+ },
+ array_vec4f: {
+ type: 'array<vec4f, 16>',
+ validity: true,
+ },
+ array_vec2h: {
+ type: 'array<vec2h, 16>',
+ validity: 'non-uniform',
+ f16: true,
+ },
+ array_vec3h: {
+ type: 'array<vec3h, 16>',
+ validity: 'non-uniform',
+ f16: true,
+ },
+ array_vec4h: {
+ type: 'array<vec4h, 16>',
+ validity: 'non-uniform',
+ f16: true,
+ },
+ array_vec2b: {
+ type: 'array<vec2<bool>, 16>',
+ validity: 'non-interface',
+ },
+ array_vec3b: {
+ type: 'array<vec3<bool>, 16>',
+ validity: 'non-interface',
+ },
+ array_vec4b: {
+ type: 'array<vec4<bool>, 16>',
+ validity: 'non-interface',
+ },
+ array_mat2x2f: {
+ type: 'array<mat2x2f, 16>',
+ validity: true,
+ },
+ array_mat2x4f: {
+ type: 'array<mat2x4f, 16>',
+ validity: true,
+ },
+ array_mat4x2f: {
+ type: 'array<mat4x2f, 16>',
+ validity: true,
+ },
+ array_mat4x4f: {
+ type: 'array<mat4x4f, 16>',
+ validity: true,
+ },
+ array_mat2x2h: {
+ type: 'array<mat2x2h, 16>',
+ validity: 'non-uniform',
+ f16: true,
+ },
+ array_mat2x4h: {
+ type: 'array<mat2x4h, 16>',
+ validity: true,
+ f16: true,
+ },
+ array_mat3x2h: {
+ type: 'array<mat3x2h, 16>',
+ validity: 'non-uniform',
+ f16: true,
+ },
+ array_mat4x2h: {
+ type: 'array<mat4x2h, 16>',
+ validity: true,
+ f16: true,
+ },
+ array_mat4x4h: {
+ type: 'array<mat4x4h, 16>',
+ validity: true,
+ f16: true,
+ },
+ array_atomic: {
+ type: 'array<atomic<u32>, 16>',
+ validity: 'atomic',
+ },
+
+ // Runtime arrays
+ runtime_array_u32: {
+ type: 'array<u32>',
+ validity: 'storage',
+ },
+ runtime_array_i32: {
+ type: 'array<i32>',
+ validity: 'storage',
+ },
+ runtime_array_f32: {
+ type: 'array<f32>',
+ validity: 'storage',
+ },
+ runtime_array_f16: {
+ type: 'array<f16>',
+ validity: 'storage',
+ f16: true,
+ },
+ runtime_array_bool: {
+ type: 'array<bool>',
+ validity: false,
+ },
+ runtime_array_vec2f: {
+ type: 'array<vec2f>',
+ validity: 'storage',
+ },
+ runtime_array_vec3f: {
+ type: 'array<vec3f>',
+ validity: 'storage',
+ },
+ runtime_array_vec4f: {
+ type: 'array<vec4f>',
+ validity: 'storage',
+ },
+ runtime_array_vec2h: {
+ type: 'array<vec2h>',
+ validity: 'storage',
+ f16: true,
+ },
+ runtime_array_vec3h: {
+ type: 'array<vec3h>',
+ validity: 'storage',
+ f16: true,
+ },
+ runtime_array_vec4h: {
+ type: 'array<vec4h>',
+ validity: 'storage',
+ f16: true,
+ },
+ runtime_array_vec2b: {
+ type: 'array<vec2<bool>>',
+ validity: false,
+ },
+ runtime_array_vec3b: {
+ type: 'array<vec3<bool>>',
+ validity: false,
+ },
+ runtime_array_vec4b: {
+ type: 'array<vec4<bool>>',
+ validity: false,
+ },
+ runtime_array_mat2x2f: {
+ type: 'array<mat2x2f>',
+ validity: 'storage',
+ },
+ runtime_array_mat2x4f: {
+ type: 'array<mat2x4f>',
+ validity: 'storage',
+ },
+ runtime_array_mat4x2f: {
+ type: 'array<mat4x2f>',
+ validity: 'storage',
+ },
+ runtime_array_mat4x4f: {
+ type: 'array<mat4x4f>',
+ validity: 'storage',
+ },
+ runtime_array_mat2x2h: {
+ type: 'array<mat2x2h>',
+ validity: 'storage',
+ f16: true,
+ },
+ runtime_array_mat2x4h: {
+ type: 'array<mat2x4h>',
+ validity: 'storage',
+ f16: true,
+ },
+ runtime_array_mat3x2h: {
+ type: 'array<mat3x2h>',
+ validity: 'storage',
+ f16: true,
+ },
+ runtime_array_mat4x2h: {
+ type: 'array<mat4x2h>',
+ validity: 'storage',
+ f16: true,
+ },
+ runtime_array_mat4x4h: {
+ type: 'array<mat4x4h>',
+ validity: 'storage',
+ f16: true,
+ },
+ runtime_array_atomic: {
+ type: 'array<atomic<u32>>',
+ validity: 'storage',
+ },
+
+ // Structs (and arrays of structs)
+ array_struct_u32: {
+ type: 'array<S, 16>',
+ decls: 'struct S { x : u32 }',
+ validity: 'non-uniform',
+ },
+ array_struct_u32_size16: {
+ type: 'array<S, 16>',
+ decls: 'struct S { @size(16) x : u32 }',
+ validity: true,
+ },
+ array_struct_vec2f: {
+ type: 'array<S, 16>',
+ decls: 'struct S { x : vec2f }',
+ validity: 'non-uniform',
+ },
+ array_struct_vec2h: {
+ type: 'array<S, 16>',
+ decls: 'struct S { x : vec2h }',
+ validity: 'non-uniform',
+ f16: true,
+ },
+ array_struct_vec2h_align16: {
+ type: 'array<S, 16>',
+ decls: 'struct S { @align(16) x : vec2h }',
+ validity: true,
+ f16: true,
+ },
+ size_too_small: {
+ type: 'S',
+ decls: 'struct S { @size(2) x : u32 }',
+ validity: false,
+ },
+ struct_padding: {
+ type: 'S',
+ decls: `struct T { x : u32 }
+ struct S { t : T, x : u32 }`,
+ validity: 'non-uniform',
+ },
+ struct_array_u32: {
+ type: 'S',
+ decls: 'struct S { x : array<u32, 4> }',
+ validity: 'non-uniform',
+ },
+ struct_runtime_array_u32: {
+ type: 'S',
+ decls: 'struct S { x : array<u32> }',
+ validity: 'storage',
+ },
+ array_struct_size_5: {
+ type: 'array<S, 16>',
+ decls: 'struct S { @size(5) x : u32, y : u32 }',
+ validity: 'non-uniform',
+ },
+ array_struct_size_5x2: {
+ type: 'array<S, 16>',
+ decls: 'struct S { @size(5) x : u32, @size(5) y : u32 }',
+ validity: true,
+ },
+ struct_size_5: {
+ type: 'S',
+ decls: `struct T { @size(5) x : u32 }
+ struct S { x : u32, y : T }`,
+ validity: 'non-uniform',
+ },
+ struct_size_5_align16: {
+ type: 'S',
+ decls: `struct T { @align(16) @size(5) x : u32 }
+ struct S { x : u32, y : T }`,
+ validity: true,
+ },
+};
+
+g.test('layout_constraints')
+ .desc('Test address space layout constraints')
+ .params(u =>
+ u
+ .combine('case', keysOf(kLayoutCases))
+ .beginSubcases()
+ .combine('aspace', ['storage', 'uniform', 'function', 'private', 'workgroup'] as const)
+ )
+ .beforeAllSubcases(t => {
+ const testcase = kLayoutCases[t.params.case];
+ if (testcase.f16) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const testcase = kLayoutCases[t.params.case];
+ const decls = testcase.decls !== undefined ? testcase.decls : '';
+ let code = `
+${testcase.f16 ? 'enable f16;' : ''}
+${decls}
+
+`;
+
+ switch (t.params.aspace) {
+ case 'storage':
+ code += `@group(0) @binding(0) var<storage, read_write> v : ${testcase.type};\n`;
+ break;
+ case 'uniform':
+ code += `@group(0) @binding(0) var<uniform> v : ${testcase.type};\n`;
+ break;
+ case 'workgroup':
+ code += `var<workgroup> v : ${testcase.type};\n`;
+ break;
+ case 'private':
+ code += `var<private> v : ${testcase.type};\n`;
+ break;
+ default:
+ break;
+ }
+
+ code += `@compute @workgroup_size(1,1,1)
+ fn main() {
+ `;
+
+ if (t.params.aspace === 'function') {
+ code += `var v : ${testcase.type};\n`;
+ }
+ code += `}\n`;
+
+ const is_interface = t.params.aspace === 'uniform' || t.params.aspace === 'storage';
+ const supports_atomic = t.params.aspace === 'storage' || t.params.aspace === 'workgroup';
+ const expect =
+ testcase.validity === true ||
+ (testcase.validity === 'non-uniform' && t.params.aspace !== 'uniform') ||
+ (testcase.validity === 'non-interface' && !is_interface) ||
+ (testcase.validity === 'storage' && t.params.aspace === 'storage') ||
+ (testcase.validity === 'atomic' && supports_atomic);
+ t.expectCompileResult(expect, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/locations.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/locations.spec.ts
index 8452679d71..3c41f1a8b5 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/locations.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/locations.spec.ts
@@ -380,3 +380,152 @@ g.test('location_fp16')
}`;
t.expectCompileResult(t.params.ext === '', code);
});
+
+interface OutOfOrderCase {
+ params?: string;
+ returnType?: string;
+ decls?: string;
+ returnValue?: string;
+ valid: boolean;
+}
+
+const kOutOfOrderCases: Record<string, OutOfOrderCase> = {
+ reverse_params: {
+ params: `@location(2) p1 : f32, @location(1) p2 : f32, @location(0) p3 : f32`,
+ valid: true,
+ },
+ no_zero_params: {
+ params: `@location(2) p1 : f32, @location(1) p2 : f32`,
+ valid: true,
+ },
+ reverse_overlap: {
+ params: `@location(2) p1 : f32, @location(1) p2 : f32, @location(1) p3 : f32`,
+ valid: false,
+ },
+ struct: {
+ params: `p1 : S`,
+ decls: `struct S {
+ @location(1) x : f32,
+ @location(0) y : f32,
+ }`,
+ valid: true,
+ },
+ struct_override: {
+ params: `@location(0) p1 : S`,
+ decls: `struct S {
+ @location(1) x : f32,
+ @location(0) y : f32,
+ }`,
+ valid: false,
+ },
+ struct_random: {
+ params: `p1 : S, p2 : T`,
+ decls: `struct S {
+ @location(16) x : f32,
+ @location(4) y : f32,
+ }
+ struct T {
+ @location(13) x : f32,
+ @location(7) y : f32,
+ }`,
+ valid: true,
+ },
+ struct_random_overlap: {
+ params: `p1 : S, p2 : T`,
+ decls: `struct S {
+ @location(16) x : f32,
+ @location(4) y : f32,
+ }
+ struct T {
+ @location(13) x : f32,
+ @location(4) y : f32,
+ }`,
+ valid: false,
+ },
+ mixed_locations1: {
+ params: `@location(12) p1 : f32, p2 : S`,
+ decls: `struct S {
+ @location(2) x : f32,
+ }`,
+ valid: true,
+ },
+ mixed_locations2: {
+ params: `p1 : S, @location(2) p2 : f32`,
+ decls: `struct S {
+ @location(12) x : f32,
+ }`,
+ valid: true,
+ },
+ mixed_overlap: {
+ params: `p1 : S, @location(12) p2 : f32`,
+ decls: `struct S {
+ @location(12) x : f32,
+ }`,
+ valid: false,
+ },
+ with_param_builtin: {
+ params: `p : S`,
+ decls: `struct S {
+ @location(12) x : f32,
+ @builtin(position) pos : vec4f,
+ @location(0) y : f32,
+ }`,
+ valid: true,
+ },
+ non_zero_return: {
+ returnType: `@location(1) vec4f`,
+ returnValue: `vec4f()`,
+ valid: true,
+ },
+ reverse_return: {
+ returnType: `S`,
+ returnValue: `S()`,
+ decls: `struct S {
+ @location(2) x : f32,
+ @location(1) y : f32,
+ @location(0) z : f32,
+ }`,
+ valid: true,
+ },
+ gap_return: {
+ returnType: `S`,
+ returnValue: `S()`,
+ decls: `struct S {
+ @location(13) x : f32,
+ @location(7) y : f32,
+ @location(2) z : f32,
+ }`,
+ valid: true,
+ },
+ with_return_builtin: {
+ returnType: `S`,
+ returnValue: `S()`,
+ decls: `struct S {
+ @location(11) x : f32,
+ @builtin(frag_depth) d : f32,
+ @location(10) y : f32,
+ }`,
+ valid: true,
+ },
+};
+
+g.test('out_of_order')
+ .desc(`Test validation of out of order locations`)
+ .params(u => u.combine('case', keysOf(kOutOfOrderCases)))
+ .fn(t => {
+ const testcase = kOutOfOrderCases[t.params.case];
+ const decls = testcase.decls !== undefined ? testcase.decls : ``;
+ const params = testcase.params !== undefined ? testcase.params : ``;
+ const returnType = testcase.returnType !== undefined ? `-> ${testcase.returnType}` : ``;
+ const returnValue = testcase.returnValue !== undefined ? `return ${testcase.returnValue};` : ``;
+ const code = `
+${decls}
+
+@fragment
+fn main(${params}) ${returnType} {
+ ${returnValue}
+}
+`;
+
+ t.expectCompileResult(testcase.valid, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/size.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/size.spec.ts
index f81dde4a1d..564c3f2b7c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/size.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/shader_io/size.spec.ts
@@ -103,7 +103,7 @@ const kSizeTests = {
};
g.test('size')
- .desc(`Test validation of ize`)
+ .desc(`Test validation of size`)
.params(u => u.combine('attr', keysOf(kSizeTests)))
.fn(t => {
const code = `
@@ -210,3 +210,21 @@ g.test('size_non_struct')
t.expectCompileResult(data.pass, code);
});
+
+g.test('size_creation_fixed_footprint')
+ .desc(`Test that @size is only valid on types that have creation-fixed footprint.`)
+ .params(u => u.combine('array_size', [', 4', '']))
+ .fn(t => {
+ const code = `
+struct S {
+ @size(64) a: array<f32${t.params.array_size}>,
+};
+@group(0) @binding(0)
+var<storage> a: S;
+
+@workgroup_size(1)
+@compute fn main() {
+ _ = a.a[0];
+}`;
+ t.expectCompileResult(t.params.array_size !== '', code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/alias.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/alias.spec.ts
index 266b4f9a12..59e8607a5f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/alias.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/alias.spec.ts
@@ -3,6 +3,7 @@ Validation tests for type aliases
`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
import { ShaderValidationTest } from '../shader_validation_test.js';
export const g = makeTestGroup(ShaderValidationTest);
@@ -121,3 +122,124 @@ alias T = ${t.params.target};
`;
t.expectCompileResult(t.params.target === 'i32', wgsl);
});
+
+const kTypes = [
+ 'bool',
+ 'i32',
+ 'u32',
+ 'f32',
+ 'f16',
+ 'vec2<i32>',
+ 'vec3<u32>',
+ 'vec4<f32>',
+ 'mat2x2<f32>',
+ 'mat2x3<f32>',
+ 'mat2x4<f32>',
+ 'mat3x2<f32>',
+ 'mat3x3<f32>',
+ 'mat3x4<f32>',
+ 'mat4x2<f32>',
+ 'mat4x3<f32>',
+ 'mat4x4<f32>',
+ 'array<u32>',
+ 'array<i32, 4>',
+ 'array<vec2<u32>, 8>',
+ 'S',
+ 'T',
+ 'atomic<u32>',
+ 'atomic<i32>',
+ 'ptr<function, u32>',
+ 'ptr<private, i32>',
+ 'ptr<workgroup, f32>',
+ 'ptr<uniform, vec2f>',
+ 'ptr<storage, vec2u>',
+ 'ptr<storage, vec3i, read>',
+ 'ptr<storage, vec4f, read_write>',
+ 'sampler',
+ 'sampler_comparison',
+ 'texture_1d<f32>',
+ 'texture_2d<u32>',
+ 'texture_2d_array<i32>',
+ 'texture_3d<f32>',
+ 'texture_cube<i32>',
+ 'texture_cube_array<u32>',
+ 'texture_multisampled_2d<f32>',
+ 'texture_depth_multisampled_2d',
+ 'texture_external',
+ 'texture_storage_1d<rgba8snorm, write>',
+ 'texture_storage_1d<r32uint, write>',
+ 'texture_storage_1d<r32sint, read_write>',
+ 'texture_storage_1d<r32float, read>',
+ 'texture_storage_2d<rgba16uint, write>',
+ 'texture_storage_2d_array<rg32float, write>',
+ 'texture_storage_3d<bgra8unorm, write>',
+ 'texture_depth_2d',
+ 'texture_depth_2d_array',
+ 'texture_depth_cube',
+ 'texture_depth_cube_array',
+
+ // Pre-declared aliases (spot check)
+ 'vec2f',
+ 'vec3u',
+ 'vec4i',
+ 'mat2x2f',
+
+ // User-defined aliases
+ 'anotherAlias',
+ 'random_alias',
+];
+
+g.test('any_type')
+ .desc('Test that any type can be aliased')
+ .params(u => u.combine('type', kTypes))
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const ty = t.params.type;
+ t.skipIf(
+ ty.includes('texture_storage') &&
+ ty.includes('read') &&
+ !t.hasLanguageFeature('readonly_and_readwrite_storage_textures'),
+ 'Missing language feature'
+ );
+ const enable = ty === 'f16' ? 'enable f16;' : '';
+ const code = `
+ ${enable}
+ struct S { x : u32 }
+ struct T { y : S }
+ alias anotherAlias = u32;
+ alias random_alias = i32;
+ alias myType = ${ty};`;
+ t.expectCompileResult(true, code);
+ });
+
+const kMatchCases = {
+ function_param: `
+ fn foo(x : u32) { }
+ fn bar() {
+ var x : alias_alias_u32;
+ foo(x);
+ }`,
+ constructor: `var<private> v : u32 = alias_u32(1);`,
+ template_param: `var<private> v : vec2<alias_u32> = vec2<u32>();`,
+ predeclared_alias: `var<private> v : vec2<alias_alias_u32> = vec2u();`,
+ struct_element: `
+ struct S { x : alias_u32 }
+ const c_u32 = 0u;
+ const c = S(c_u32);`,
+};
+
+g.test('match_non_alias')
+ .desc('Test that type checking succeeds using aliased and unaliased type')
+ .params(u => u.combine('case', keysOf(kMatchCases)))
+ .fn(t => {
+ const testcase = kMatchCases[t.params.case];
+ const code = `
+ alias alias_u32 = u32;
+ alias alias_alias_u32 = alias_u32;
+ ${testcase}`;
+ t.expectCompileResult(true, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/array.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/array.spec.ts
new file mode 100644
index 0000000000..636906f52e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/array.spec.ts
@@ -0,0 +1,122 @@
+export const description = `
+Validation tests for array types
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidCases = {
+ // Basic element types.
+ i32: `alias T = array<i32>;`,
+ u32: `alias T = array<u32>;`,
+ f32: `alias T = array<f32>;`,
+ f16: `enable f16;\nalias T = array<f16>;`,
+ bool: `alias T = array<bool>;`,
+
+ // Composite elements
+ vec2u: `alias T = array<vec2u>;`,
+ vec3i: `alias T = array<vec3i>;`,
+ vec4f: `alias T = array<vec4f>;`,
+ array: `alias T = array<array<u32, 4>>;`,
+ struct: `struct S { x : u32 }\nalias T = array<S>;`,
+ mat2x2f: `alias T = array<mat2x2f>;`,
+ mat4x4h: `enable f16;\nalias T = array<mat4x4h>;`,
+
+ // Atomic elements
+ atomicu: `alias T = array<atomic<u32>>;`,
+ atomici: `alias T = array<atomic<i32>>;`,
+
+ // Count expressions
+ literal_count: `alias T = array<u32, 4>;`,
+ literali_count: `alias T = array<u32, 4i>;`,
+ literalu_count: `alias T = array<u32, 4u>;`,
+ const_count: `const x = 8;\nalias T = array<u32, x>;`,
+ const_expr_count1: `alias T = array<u32, 1 + 3>;`,
+ const_expr_count2: `const x = 4;\nalias T = array<u32, x * 2>;`,
+ override_count: `override x : u32;\nalias T = array<u32, x>;`,
+ override_expr1: `override x = 2;\nalias T = array<u32, vec2(x,x).x>;`,
+ override_expr2: `override x = 1;\nalias T = array<u32, x + 1>;`,
+ override_zero: `override x = 0;\nalias T = array<u32, x>;`,
+ override_neg: `override x = -1;\nalias T = array<u32, x>;`,
+
+ // Same array types
+ same_const_value1: `
+ const x = 8;
+ const y = 8;
+ var<private> v : array<u32, x> = array<u32, y>();`,
+ same_const_value2: `
+ const x = 8;
+ var<private> v : array<u32, x> = array<u32, 8>();`,
+ same_const_value3: `
+ var<private> v : array<u32, 8i> = array<u32, 8u>();`,
+ same_override: `
+ requires unrestricted_pointer_parameters;
+ override x : u32;
+ var<workgroup> v : array<u32, x>;
+ fn bar(p : ptr<workgroup, array<u32, x>>) { }
+ fn foo() { bar(&v); }`,
+
+ // Shadow
+ shadow: `alias array = vec2f;`,
+};
+
+g.test('valid')
+ .desc('Valid array type tests')
+ .params(u => u.combine('case', keysOf(kValidCases)))
+ .beforeAllSubcases(t => {
+ const code = kValidCases[t.params.case];
+ if (code.indexOf('f16') >= 0) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const code = kValidCases[t.params.case];
+ t.skipIf(
+ code.indexOf('unrestricted') >= 0 && !t.hasLanguageFeature('unrestricted_pointer_parameters'),
+ 'Test requires unrestricted_pointer_parameters'
+ );
+ t.expectCompileResult(true, code);
+ });
+
+const kInvalidCases = {
+ f16_without_enable: `alias T = array<f16>;`,
+ runtime_nested: `alias T = array<array<u32>, 4>;`,
+ override_nested: `
+ override x : u32;
+ alias T = array<array<u32, x>, 4>;`,
+ override_nested_struct: `
+ override x : u32;
+ struct T { x : array<u32, x> }`,
+ zero_size: `alias T = array<u32, 0>;`,
+ negative_size: `alias T = array<u32, 1 - 2>;`,
+ const_zero: `const x = 0;\nalias T = array<u32, x>;`,
+ const_neg: `const x = 1;\nconst y = 2;\nalias T = array<u32, x - y>;`,
+ incompatible_overrides: `
+ requires unrestricted_pointer_parameters;
+ override x = 8;
+ override y = 8;
+ var<workgroup> v : array<u32, x>
+ fn bar(p : ptr<workgroup, array<u32 y>>) { }
+ fn foo() { bar(&v); }`,
+};
+
+g.test('invalid')
+ .desc('Invalid array type tests')
+ .params(u => u.combine('case', keysOf(kInvalidCases)))
+ .beforeAllSubcases(t => {
+ const code = kInvalidCases[t.params.case];
+ if (code.indexOf('f16') >= 0) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const code = kInvalidCases[t.params.case];
+ t.skipIf(
+ code.indexOf('unrestricted') >= 0 && !t.hasLanguageFeature('unrestricted_pointer_parameters'),
+ 'Test requires unrestricted_pointer_parameters'
+ );
+ t.expectCompileResult(false, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/atomics.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/atomics.spec.ts
new file mode 100644
index 0000000000..36c37176e8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/atomics.spec.ts
@@ -0,0 +1,145 @@
+export const description = `
+Validation tests for atomic types
+
+Tests covered:
+* Base type
+* Address spaces
+* Invalid operations (non-exhaustive)
+
+Note: valid operations (e.g. atomic built-in functions) are tested in the builtin tests.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('type')
+ .desc('Test of the underlying atomic data type')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#atomic-types')
+ .params(u =>
+ u.combine('type', [
+ 'u32',
+ 'i32',
+ 'f32',
+ 'f16',
+ 'bool',
+ 'vec2u',
+ 'vec3i',
+ 'vec4f',
+ 'mat2x2f',
+ 'R',
+ 'S',
+ 'array<u32, 1>',
+ 'array<i32, 4>',
+ 'array<u32>',
+ 'array<i32>',
+ 'atomic<u32>',
+ 'atomic<i32>',
+ ] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.type === 'f16') {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const code = `
+struct S {
+ x : u32
+}
+struct T {
+ x : i32
+}
+struct R {
+ x : f32
+}
+
+struct Test {
+ x : atomic<${t.params.type}>
+}
+`;
+
+ const expect = t.params.type === 'u32' || t.params.type === 'i32';
+ t.expectCompileResult(expect, code);
+ });
+
+g.test('address_space')
+ .desc('Test allowed address spaces for atomics')
+ .specURL('https://gpuweb.github.io/gpuweb/wgsl/#atomic-types')
+ .params(u =>
+ u
+ .combine('aspace', [
+ 'storage',
+ 'workgroup',
+ 'storage-ro',
+ 'uniform',
+ 'private',
+ 'function',
+ 'function-let',
+ ] as const)
+ .beginSubcases()
+ .combine('type', ['i32', 'u32'] as const)
+ )
+ .fn(t => {
+ let moduleVar = ``;
+ let functionVar = '';
+ switch (t.params.aspace) {
+ case 'storage-ro':
+ moduleVar = `@group(0) @binding(0) var<storage> x : atomic<${t.params.type}>;\n`;
+ break;
+ case 'storage':
+ moduleVar = `@group(0) @binding(0) var<storage, read_write> x : atomic<${t.params.type}>;\n`;
+ break;
+ case 'uniform':
+ moduleVar = `@group(0) @binding(0) var<uniform> x : atomic<${t.params.type}>;\n`;
+ break;
+ case 'workgroup':
+ case 'private':
+ moduleVar = `var<${t.params.aspace}> x : atomic<${t.params.type}>;\n`;
+ break;
+ case 'function':
+ functionVar = `var x : atomic<${t.params.type}>;\n`;
+ break;
+ case 'function-let':
+ functionVar = `let x : atomic<${t.params.type}>;\n`;
+ break;
+ }
+ const code = `
+${moduleVar}
+
+fn foo() {
+ ${functionVar}
+}
+`;
+
+ const expect = t.params.aspace === 'storage' || t.params.aspace === 'workgroup';
+ t.expectCompileResult(expect, code);
+ });
+
+const kInvalidOperations = {
+ add: `a1 + a2`,
+ load: `a1`,
+ store: `a1 = 1u`,
+ deref: `*a1 = 1u`,
+ equality: `a1 == a2`,
+ abs: `abs(a1)`,
+ address_abs: `abs(&a1)`,
+};
+
+g.test('invalid_operations')
+ .desc('Tests that a selection of invalid operations are invalid')
+ .params(u => u.combine('op', keysOf(kInvalidOperations)))
+ .fn(t => {
+ const code = `
+var<workgroup> a1 : atomic<u32>;
+var<workgroup> a2 : atomic<u32>;
+
+fn foo() {
+ let x : u32 = ${kInvalidOperations[t.params.op]};
+}
+`;
+
+ t.expectCompileResult(false, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/matrix.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/matrix.spec.ts
new file mode 100644
index 0000000000..02458c6c88
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/matrix.spec.ts
@@ -0,0 +1,152 @@
+export const description = `
+Validation tests for matrix types
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { keysOf } from '../../../../common/util/data_tables.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+const kValidCases = {
+ // Basic matrices
+ mat2x2_f32: `alias T = mat2x2<f32>;`,
+ mat2x3_f32: `alias T = mat2x3<f32>;`,
+ mat2x4_f32: `alias T = mat2x4<f32>;`,
+ mat3x2_f32: `alias T = mat3x2<f32>;`,
+ mat3x3_f32: `alias T = mat3x3<f32>;`,
+ mat3x4_f32: `alias T = mat3x4<f32>;`,
+ mat4x2_f32: `alias T = mat4x2<f32>;`,
+ mat4x3_f32: `alias T = mat4x3<f32>;`,
+ mat4x4_f32: `alias T = mat4x4<f32>;`,
+ mat2x2_f16: `enable f16;\nalias T = mat2x2<f16>;`,
+ mat2x3_f16: `enable f16;\nalias T = mat2x3<f16>;`,
+ mat2x4_f16: `enable f16;\nalias T = mat2x4<f16>;`,
+ mat3x2_f16: `enable f16;\nalias T = mat3x2<f16>;`,
+ mat3x3_f16: `enable f16;\nalias T = mat3x3<f16>;`,
+ mat3x4_f16: `enable f16;\nalias T = mat3x4<f16>;`,
+ mat4x2_f16: `enable f16;\nalias T = mat4x2<f16>;`,
+ mat4x3_f16: `enable f16;\nalias T = mat4x3<f16>;`,
+ mat4x4_f16: `enable f16;\nalias T = mat4x4<f16>;`,
+
+ // Pre-declared aliases
+ mat2x2f: `alias T = mat2x2f;`,
+ mat2x3f: `alias T = mat2x3f;`,
+ mat2x4f: `alias T = mat2x4f;`,
+ mat3x2f: `alias T = mat3x2f;`,
+ mat3x3f: `alias T = mat3x3f;`,
+ mat3x4f: `alias T = mat3x4f;`,
+ mat4x2f: `alias T = mat4x2f;`,
+ mat4x3f: `alias T = mat4x3f;`,
+ mat4x4f: `alias T = mat4x4f;`,
+ mat2x2h: `enable f16;\nalias T = mat2x2h;`,
+ mat2x3h: `enable f16;\nalias T = mat2x3h;`,
+ mat2x4h: `enable f16;\nalias T = mat2x4h;`,
+ mat3x2h: `enable f16;\nalias T = mat3x2h;`,
+ mat3x3h: `enable f16;\nalias T = mat3x3h;`,
+ mat3x4h: `enable f16;\nalias T = mat3x4h;`,
+ mat4x2h: `enable f16;\nalias T = mat4x2h;`,
+ mat4x3h: `enable f16;\nalias T = mat4x3h;`,
+ mat4x4h: `enable f16;\nalias T = mat4x4h;`,
+
+ trailing_comman: `alias T = mat2x2<f32,>;`,
+
+ // Abstract matrices
+ abstract_2x2: `const m = mat2x2(1,1,1,1);`,
+ abstract_2x3: `const m = mat2x3(1,1,1,1,1,1);`,
+ abstract_2x4: `const m = mat2x4(1,1,1,1,1,1,1,1);`,
+
+ // Base roots shadowable
+ shadow_mat2x2: `alias mat2x2 = array<vec2f, 2>;`,
+ shadow_mat2x3: `alias mat2x3 = array<vec2f, 3>;`,
+ shadow_mat2x4: `alias mat2x4 = array<vec2f, 4>;`,
+ shadow_mat3x2: `alias mat3x2 = array<vec3f, 2>;`,
+ shadow_mat3x3: `alias mat3x3 = array<vec3f, 3>;`,
+ shadow_mat3x4: `alias mat3x4 = array<vec3f, 4>;`,
+ shadow_mat4x2: `alias mat4x2 = array<vec4f, 2>;`,
+ shadow_mat4x3: `alias mat4x3 = array<vec4f, 3>;`,
+ shadow_mat4x4: `alias mat4x4 = array<vec4f, 4>;`,
+
+ // Pre-declared aliases shadowable
+ shadow_mat2x2f: `alias mat2x2f = mat2x2<f32>;`,
+ shadow_mat2x3f: `alias mat2x3f = mat2x3<f32>;`,
+ shadow_mat2x4f: `alias mat2x4f = mat2x4<f32>;`,
+ shadow_mat3x2f: `alias mat3x2f = mat3x2<f32>;`,
+ shadow_mat3x3f: `alias mat3x3f = mat3x3<f32>;`,
+ shadow_mat3x4f: `alias mat3x4f = mat3x4<f32>;`,
+ shadow_mat4x2f: `alias mat4x2f = mat4x2<f32>;`,
+ shadow_mat4x3f: `alias mat4x3f = mat4x3<f32>;`,
+ shadow_mat4x4f: `alias mat4x4f = mat4x4<f32>;`,
+ shadow_mat2x2h: `enable f16;\nalias mat2x2h = mat2x2<f16>;`,
+ shadow_mat2x3h: `enable f16;\nalias mat2x3h = mat2x3<f16>;`,
+ shadow_mat2x4h: `enable f16;\nalias mat2x4h = mat2x4<f16>;`,
+ shadow_mat3x2h: `enable f16;\nalias mat3x2h = mat3x2<f16>;`,
+ shadow_mat3x3h: `enable f16;\nalias mat3x3h = mat3x3<f16>;`,
+ shadow_mat3x4h: `enable f16;\nalias mat3x4h = mat3x4<f16>;`,
+ shadow_mat4x2h: `enable f16;\nalias mat4x2h = mat4x2<f16>;`,
+ shadow_mat4x3h: `enable f16;\nalias mat4x3h = mat4x3<f16>;`,
+ shadow_mat4x4h: `enable f16;\nalias mat4x4h = mat4x4<f16>;`,
+};
+
+g.test('valid')
+ .desc('Valid matrix type tests')
+ .params(u => u.combine('case', keysOf(kValidCases)))
+ .beforeAllSubcases(t => {
+ const code = kValidCases[t.params.case];
+ if (code.indexOf('f16') >= 0) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const code = kValidCases[t.params.case];
+ t.expectCompileResult(true, code);
+ });
+
+const kInvalidCases = {
+ // Invalid component types
+ mat2x2_i32: `alias T = mat2x2<i32>;`,
+ mat3x3_u32: `alias T = mat3x3<u32>;`,
+ mat4x4_bool: `alias T = mat4x4<bool>;`,
+ mat2x2_vec4f: `alias T = mat2x2<vec2f>;`,
+ mat2x2_array: `alias T = mat2x2<array<f32, 2>>;`,
+ mat2x2_struct: `struct S { x : f32 }\nalias T = mat2x2<S>;`,
+
+ // Invalid dimensions
+ mat1x1: `alias T = mat1x1<f32>;`,
+ mat2x1: `alias T = mat2x1<f32>;`,
+ mat2x5: `alias T = mat2x5<f32>;`,
+ mat5x5: `alias T = mat5x5<f32>;`,
+
+ // Half-precision aliases require enable
+ no_enable_mat2x2h: `alias T = mat2x2h;`,
+ no_enable_mat2x3h: `alias T = mat2x3h;`,
+ no_enable_mat2x4h: `alias T = mat2x4h;`,
+ no_enable_mat3x2h: `alias T = mat3x2h;`,
+ no_enable_mat3x3h: `alias T = mat3x3h;`,
+ no_enable_mat3x4h: `alias T = mat3x4h;`,
+ no_enable_mat4x2h: `alias T = mat4x2h;`,
+ no_enable_mat4x3h: `alias T = mat4x3h;`,
+ no_enable_mat4x4h: `alias T = mat4x4h;`,
+
+ missing_template: `alias T = mat2x2;`,
+ missing_left_template: `alias T = mat2x2f32>;`,
+ missing_right_template: `alias T = mat2x2<f32;`,
+ missing_comp: `alias T = mat2x2<>;`,
+ mat2x2i: `alias T = mat2x2i;`,
+ mat2x2u: `alias T = mat2x2u;`,
+ mat2x2b: `alias T = mat2x2b;`,
+};
+
+g.test('invalid')
+ .desc('Invalid matrix type tests')
+ .params(u => u.combine('case', keysOf(kInvalidCases)))
+ .beforeAllSubcases(t => {
+ const code = kInvalidCases[t.params.case];
+ if (code.indexOf('f16') >= 0) {
+ t.selectDeviceOrSkipTestCase('shader-f16');
+ }
+ })
+ .fn(t => {
+ const code = kInvalidCases[t.params.case];
+ t.expectCompileResult(false, code);
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/textures.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/textures.spec.ts
new file mode 100644
index 0000000000..59e5b26db6
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/types/textures.spec.ts
@@ -0,0 +1,170 @@
+export const description = `
+Validation tests for various texture types in shaders.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import {
+ isTextureFormatUsableAsStorageFormat,
+ kAllTextureFormats,
+ kColorTextureFormats,
+ kTextureFormatInfo,
+} from '../../../format_info.js';
+import { getPlainTypeInfo } from '../../../util/shader.js';
+import { ShaderValidationTest } from '../shader_validation_test.js';
+
+export const g = makeTestGroup(ShaderValidationTest);
+
+g.test('texel_formats')
+ .desc(
+ 'Test channels and channel format of various texel formats when using as the storage texture format'
+ )
+ .params(u =>
+ u
+ .combine('format', kColorTextureFormats)
+ .filter(p => kTextureFormatInfo[p.format].color.storage)
+ .beginSubcases()
+ .combine('shaderScalarType', ['f32', 'u32', 'i32', 'bool', 'f16'] as const)
+ )
+ .beforeAllSubcases(t => {
+ if (t.params.shaderScalarType === 'f16') {
+ t.selectDeviceOrSkipTestCase({ requiredFeatures: ['shader-f16'] });
+ }
+
+ if (!isTextureFormatUsableAsStorageFormat(t.params.format, t.isCompatibility)) {
+ t.skip('storage usage is unsupported');
+ }
+ })
+ .fn(t => {
+ const { format, shaderScalarType } = t.params;
+ const info = kTextureFormatInfo[format];
+ const validShaderScalarType = getPlainTypeInfo(info.color.type);
+ const shaderValueType = `vec4<${shaderScalarType}>`;
+ const wgsl = `
+ @group(0) @binding(0) var tex: texture_storage_2d<${format}, read>;
+ @compute @workgroup_size(1) fn main() {
+ let v : ${shaderValueType} = textureLoad(tex, vec2u(0));
+ _ = v;
+ }
+`;
+ t.expectCompileResult(validShaderScalarType === shaderScalarType, wgsl);
+ });
+
+g.test('texel_formats,as_value')
+ .desc('Test that texel format cannot be used as value')
+ .fn(t => {
+ const wgsl = `
+ @compute @workgroup_size(1) fn main() {
+ var i = rgba8unorm;
+ }
+`;
+ t.expectCompileResult(false, wgsl);
+ });
+
+const kValidTextureSampledTypes = ['f32', 'i32', 'u32'];
+
+g.test('sampled_texture_types')
+ .desc(
+ `Test that for texture_xx<T>
+- The sampled type T must be f32, i32, or u32
+`
+ )
+ .params(u =>
+ u
+ .combine('textureType', ['texture_2d', 'texture_multisampled_2d'])
+ .beginSubcases()
+ .combine('sampledType', [
+ ...kValidTextureSampledTypes,
+ 'bool',
+ 'vec2',
+ 'mat2x2',
+ '1.0',
+ '1',
+ '1u',
+ ] as const)
+ )
+ .fn(t => {
+ const { textureType, sampledType } = t.params;
+ const wgsl = `@group(0) @binding(0) var tex: ${textureType}<${sampledType}>;`;
+ t.expectCompileResult(kValidTextureSampledTypes.includes(sampledType), wgsl);
+ });
+
+g.test('external_sampled_texture_types')
+ .desc(
+ `Test that texture_extenal compiles and cannot specify address space
+`
+ )
+ .fn(t => {
+ t.expectCompileResult(true, `@group(0) @binding(0) var tex: texture_external;`);
+ t.expectCompileResult(false, `@group(0) @binding(0) var<private> tex: texture_external;`);
+ });
+
+const kAccessModes = ['read', 'write', 'read_write'];
+
+g.test('storage_texture_types')
+ .desc(
+ `Test that for texture_storage_xx<format, access>
+- format must be an enumerant for one of the texel formats for storage textures
+- access must be an enumerant for one of the access modes
+
+Besides, the shader compilation should always pass regardless of whether the format supports the usage indicated by the access or not.
+`
+ )
+ .params(u =>
+ u.combine('access', [...kAccessModes, 'storage'] as const).combine('format', kAllTextureFormats)
+ )
+ .fn(t => {
+ const { format, access } = t.params;
+ const info = kTextureFormatInfo[format];
+ // bgra8unorm is considered a valid storage format at shader compilation stage
+ const isFormatValid =
+ info.color?.storage ||
+ info.depth?.storage ||
+ info.stencil?.storage ||
+ format === 'bgra8unorm';
+ const isAccessValid = kAccessModes.includes(access);
+ const wgsl = `@group(0) @binding(0) var tex: texture_storage_2d<${format}, ${access}>;`;
+ t.expectCompileResult(isFormatValid && isAccessValid, wgsl);
+ });
+
+g.test('depth_texture_types')
+ .desc(
+ `Test that for texture_depth_xx
+- must not specify an address space
+`
+ )
+ .params(u =>
+ u.combine('textureType', [
+ 'texture_depth_2d',
+ 'texture_depth_2d_array',
+ 'texture_depth_cube',
+ 'texture_depth_cube_array',
+ ])
+ )
+ .fn(t => {
+ const { textureType } = t.params;
+ t.expectCompileResult(true, `@group(0) @binding(0) var t: ${textureType};`);
+ t.expectCompileResult(false, `@group(0) @binding(0) var<private> t: ${textureType};`);
+ t.expectCompileResult(false, `@group(0) @binding(0) var<storage, read> t: ${textureType};`);
+ });
+
+g.test('sampler_types')
+ .desc(
+ `Test that for sampler and sampler_comparison
+- cannot specify address space
+- cannot be declared in WGSL function scope
+`
+ )
+ .params(u => u.combine('samplerType', ['sampler', 'sampler_comparison']))
+ .fn(t => {
+ const { samplerType } = t.params;
+ t.expectCompileResult(true, `@group(0) @binding(0) var s: ${samplerType};`);
+ t.expectCompileResult(false, `@group(0) @binding(0) var<private> s: ${samplerType};`);
+ t.expectCompileResult(
+ false,
+ `
+ @compute @workgroup_size(1) fn main() {
+ var s: ${samplerType};
+ }
+ `
+ );
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/uniformity/uniformity.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/uniformity/uniformity.spec.ts
index 41249e445d..c794bded28 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/uniformity/uniformity.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/shader/validation/uniformity/uniformity.spec.ts
@@ -21,6 +21,7 @@ const kCollectiveOps = [
{ op: 'fwidthCoarse', stage: 'fragment' },
{ op: 'fwidthFine', stage: 'fragment' },
{ op: 'storageBarrier', stage: 'compute' },
+ { op: 'textureBarrier', stage: 'compute' },
{ op: 'workgroupBarrier', stage: 'compute' },
{ op: 'workgroupUniformLoad', stage: 'compute' },
];
@@ -43,6 +44,8 @@ const kConditions = [
{ cond: 'nonuniform_and2', expectation: false },
{ cond: 'uniform_func_var', expectation: true },
{ cond: 'nonuniform_func_var', expectation: false },
+ { cond: 'storage_texture_ro', expectation: true },
+ { cond: 'storage_texture_rw', expectation: false },
];
function generateCondition(condition: string): string {
@@ -98,6 +101,12 @@ function generateCondition(condition: string): string {
case 'nonuniform_func_var': {
return `n_f == 0`;
}
+ case 'storage_texture_ro': {
+ return `textureLoad(ro_storage_texture, vec2()).x == 0`;
+ }
+ case 'storage_texture_rw': {
+ return `textureLoad(rw_storage_texture, vec2()).x == 0`;
+ }
default: {
unreachable(`Unhandled condition`);
}
@@ -116,6 +125,7 @@ function generateOp(op: string): string {
return `let x = ${op}(tex_depth, s_comp, vec2(0,0), 0);\n`;
}
case 'storageBarrier':
+ case 'textureBarrier':
case 'workgroupBarrier': {
return `${op}();\n`;
}
@@ -181,12 +191,16 @@ g.test('basics')
.desc(`Test collective operations in simple uniform or non-uniform control flow.`)
.params(u =>
u
- .combineWithParams(kCollectiveOps)
- .combineWithParams(kConditions)
.combine('statement', ['if', 'for', 'while', 'switch'] as const)
.beginSubcases()
+ .combineWithParams(kConditions)
+ .combineWithParams(kCollectiveOps)
)
.fn(t => {
+ if (t.params.op === 'textureBarrier' || t.params.cond.startsWith('storage_texture')) {
+ t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
+ }
+
let code = `
@group(0) @binding(0) var s : sampler;
@group(0) @binding(1) var s_comp : sampler_comparison;
@@ -197,6 +211,9 @@ g.test('basics')
@group(1) @binding(1) var<storage, read_write> rw_buffer : array<f32, 4>;
@group(1) @binding(2) var<uniform> uniform_buffer : vec4<f32>;
+ @group(2) @binding(0) var ro_storage_texture : texture_storage_2d<rgba8unorm, read>;
+ @group(2) @binding(1) var rw_storage_texture : texture_storage_2d<rgba8unorm, read_write>;
+
var<private> priv_var : array<f32, 4> = array(0,0,0,0);
const c = false;
@@ -367,7 +384,14 @@ function generatePointerCheck(check: string): string {
}
}
-const kPointerCases = {
+interface PointerCase {
+ code: string;
+ check: 'address' | 'contents';
+ uniform: boolean | 'never';
+ needs_deref_sugar?: boolean;
+}
+
+const kPointerCases: Record<string, PointerCase> = {
address_uniform_literal: {
code: `let ptr = &wg_array[0];`,
check: `address`,
@@ -585,6 +609,168 @@ const kPointerCases = {
check: `contents`,
uniform: false,
},
+ contents_lhs_ref_pointer_deref1: {
+ code: `*&func_scalar = uniform_value;
+ let test_val = func_scalar;`,
+ check: `contents`,
+ uniform: true,
+ },
+ contents_lhs_ref_pointer_deref1a: {
+ code: `*&func_scalar = nonuniform_value;
+ let test_val = func_scalar;`,
+ check: `contents`,
+ uniform: false,
+ },
+ contents_lhs_ref_pointer_deref2: {
+ code: `*&(func_array[nonuniform_value]) = uniform_value;
+ let test_val = func_array[0];`,
+ check: `contents`,
+ uniform: false,
+ },
+ contents_lhs_ref_pointer_deref2a: {
+ code: `(func_array[nonuniform_value]) = uniform_value;
+ let test_val = func_array[0];`,
+ check: `contents`,
+ uniform: false,
+ },
+ contents_lhs_ref_pointer_deref3: {
+ code: `*&(func_array[needs_uniform(uniform_value)]) = uniform_value;
+ let test_val = func_array[0];`,
+ check: `contents`,
+ uniform: true,
+ },
+ contents_lhs_ref_pointer_deref3a: {
+ code: `*&(func_array[needs_uniform(nonuniform_value)]) = uniform_value;
+ let test_val = func_array[0];`,
+ check: `contents`,
+ uniform: 'never',
+ },
+ contents_lhs_ref_pointer_deref4: {
+ code: `*&((*&(func_struct.x[uniform_value])).x[uniform_value].x[uniform_value]) = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: true,
+ },
+ contents_lhs_ref_pointer_deref4a: {
+ code: `*&((*&(func_struct.x[uniform_value])).x[uniform_value].x[uniform_value]) = nonuniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: false,
+ },
+ contents_lhs_ref_pointer_deref4b: {
+ code: `*&((*&(func_struct.x[uniform_value])).x[uniform_value].x[nonuniform_value]) = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: false,
+ },
+ contents_lhs_ref_pointer_deref4c: {
+ code: `*&((*&(func_struct.x[uniform_value])).x[nonuniform_value]).x[uniform_value] = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: false,
+ },
+ contents_lhs_ref_pointer_deref4d: {
+ code: `*&((*&(func_struct.x[nonuniform_value])).x[uniform_value].x)[uniform_value] = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: false,
+ },
+ contents_lhs_ref_pointer_deref4e: {
+ code: `*&((*&(func_struct.x[uniform_value])).x[needs_uniform(nonuniform_value)].x[uniform_value]) = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: 'never',
+ },
+
+ // The following cases require the 'pointer_composite_access' language feature.
+ contents_lhs_pointer_deref2: {
+ code: `(&func_array)[uniform_value] = uniform_value;
+ let test_val = func_array[0];`,
+ check: `contents`,
+ uniform: true,
+ needs_deref_sugar: true,
+ },
+ contents_lhs_pointer_deref2a: {
+ code: `(&func_array)[nonuniform_value] = uniform_value;
+ let test_val = func_array[0];`,
+ check: `contents`,
+ uniform: false,
+ needs_deref_sugar: true,
+ },
+ contents_lhs_pointer_deref3: {
+ code: `(&func_array)[needs_uniform(uniform_value)] = uniform_value;
+ let test_val = func_array[0];`,
+ check: `contents`,
+ uniform: true,
+ needs_deref_sugar: true,
+ },
+ contents_lhs_pointer_deref3a: {
+ code: `(&func_array)[needs_uniform(nonuniform_value)] = uniform_value;
+ let test_val = func_array[0];`,
+ check: `contents`,
+ uniform: 'never',
+ needs_deref_sugar: true,
+ },
+ contents_lhs_pointer_deref4: {
+ code: `(&((&(func_struct.x[uniform_value])).x[uniform_value]).x)[uniform_value] = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: true,
+ needs_deref_sugar: true,
+ },
+ contents_lhs_pointer_deref4a: {
+ code: `(&((&(func_struct.x[uniform_value])).x[uniform_value]).x)[uniform_value] = nonuniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: false,
+ needs_deref_sugar: true,
+ },
+ contents_lhs_pointer_deref4b: {
+ code: `(&((&(func_struct.x[uniform_value])).x)[uniform_value]).x[nonuniform_value] = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: false,
+ needs_deref_sugar: true,
+ },
+ contents_lhs_pointer_deref4c: {
+ code: `(&((&(func_struct.x[uniform_value])).x[nonuniform_value]).x)[uniform_value] = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: false,
+ needs_deref_sugar: true,
+ },
+ contents_lhs_pointer_deref4d: {
+ code: `(&((&(func_struct.x[nonuniform_value])).x[uniform_value]).x)[uniform_value] = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: false,
+ needs_deref_sugar: true,
+ },
+ contents_lhs_pointer_deref4e: {
+ code: `(&((&(func_struct.x[uniform_value])).x)[needs_uniform(nonuniform_value)].x[uniform_value]) = uniform_value;
+ let test_val = func_struct.x[0].x[0].x[0];`,
+ check: `contents`,
+ uniform: 'never',
+ needs_deref_sugar: true,
+ },
+ contents_rhs_pointer_deref1: {
+ code: `let test_val = (&func_array)[uniform_value];`,
+ check: `contents`,
+ uniform: true,
+ needs_deref_sugar: true,
+ },
+ contents_rhs_pointer_deref1a: {
+ code: `let test_val = (&func_array)[nonuniform_value];`,
+ check: `contents`,
+ uniform: false,
+ needs_deref_sugar: true,
+ },
+ contents_rhs_pointer_deref2: {
+ code: `let test_val = (&func_array)[needs_uniform(nonuniform_value)];`,
+ check: `contents`,
+ uniform: `never`,
+ needs_deref_sugar: true,
+ },
};
g.test('pointers')
@@ -612,6 +798,13 @@ var<storage> uniform_value : u32;
@group(0) @binding(1)
var<storage, read_write> nonuniform_value : u32;
+fn needs_uniform(val : u32) -> u32{
+ if val == 0 {
+ workgroupBarrier();
+ }
+ return val;
+}
+
@compute @workgroup_size(16, 1, 1)
fn main(@builtin(local_invocation_id) lid : vec3<u32>,
@builtin(global_invocation_id) gid : vec3<u32>) {
@@ -627,11 +820,16 @@ fn main(@builtin(local_invocation_id) lid : vec3<u32>,
`
${generatePointerCheck(testcase.check)}
}`;
- if (!testcase.uniform) {
+
+ if (testcase.needs_deref_sugar === true) {
+ t.skipIfLanguageFeatureNotSupported('pointer_composite_access');
+ }
+ // Explicitly check false to distinguish from never.
+ if (testcase.uniform === false) {
const without_check = code + `}\n`;
t.expectCompileResult(true, without_check);
}
- t.expectCompileResult(testcase.uniform, with_check);
+ t.expectCompileResult(testcase.uniform === true, with_check);
});
function expectedUniformity(uniform: string, init: string): boolean {
@@ -2019,6 +2217,7 @@ g.test('binary_expressions')
u
.combine('e1', keysOf(kExpressionCases))
.combine('e2', keysOf(kExpressionCases))
+ .beginSubcases()
.combine('op', keysOf(kBinOps))
)
.fn(t => {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/binary_stream.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/binary_stream.ts
index a6512020e6..2531521dc4 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/binary_stream.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/binary_stream.ts
@@ -85,6 +85,16 @@ export default class BinaryStream {
return this.view.getInt16(this.alignedOffset(2), /* littleEndian */ true);
}
+ /** writeI64() writes a bitint to the buffer at the next 64-bit aligned offset */
+ writeI64(value: bigint) {
+ this.view.setBigInt64(this.alignedOffset(8), value, /* littleEndian */ true);
+ }
+
+ /** readI64() reads a bigint from the buffer at the next 64-bit aligned offset */
+ readI64(): bigint {
+ return this.view.getBigInt64(this.alignedOffset(8), /* littleEndian */ true);
+ }
+
/** writeI32() writes a int32 to the buffer at the next 32-bit aligned offset */
writeI32(value: number) {
this.view.setInt32(this.alignedOffset(4), value, /* littleEndian */ true);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/check_contents.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/check_contents.ts
index 298e7ae4a9..ed71d85d35 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/check_contents.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/check_contents.ts
@@ -19,7 +19,7 @@ import { generatePrettyTable } from './pretty_diff_tables.js';
/** Generate an expected value at `index`, to test for equality with the actual value. */
export type CheckElementsGenerator = (index: number) => number;
/** Check whether the actual `value` at `index` is as expected. */
-export type CheckElementsPredicate = (index: number, value: number) => boolean;
+export type CheckElementsPredicate = (index: number, value: number | bigint) => boolean;
/**
* Provides a pretty-printing implementation for a particular CheckElementsPredicate.
* This is an array; each element provides info to print an additional row in the error message.
@@ -29,9 +29,9 @@ export type CheckElementsSupplementalTableRows = Array<{
leftHeader: string;
/**
* Get the value for a cell in the table with element index `index`.
- * May be a string or a number; a number will be formatted according to the TypedArray type used.
+ * May be a string or numeric (number | bigint); numerics will be formatted according to the TypedArray type used.
*/
- getValueForCell: (index: number) => number | string;
+ getValueForCell: (index: number) => string | number | bigint;
}>;
/**
@@ -43,7 +43,10 @@ export function checkElementsEqual(
expected: TypedArrayBufferView
): ErrorWithExtra | undefined {
assert(actual.constructor === expected.constructor, 'TypedArray type mismatch');
- assert(actual.length === expected.length, 'size mismatch');
+ assert(
+ actual.length === expected.length,
+ `length mismatch: expected ${expected.length} got ${actual.length}`
+ );
let failedElementsFirstMaybe: number | undefined = undefined;
/** Sparse array with `true` for elements that failed. */
@@ -221,13 +224,17 @@ function failCheckElements({
const printElementsEnd = Math.min(size, failedElementsLast + 2);
const printElementsCount = printElementsEnd - printElementsStart;
- const numberToString = printAsFloat
- ? (n: number) => n.toPrecision(4)
- : (n: number) => intToPaddedHex(n, { byteLength: ctor.BYTES_PER_ELEMENT });
+ const numericToString = (val: number | bigint): string => {
+ if (typeof val === 'number' && printAsFloat) {
+ return val.toPrecision(4);
+ }
+ return intToPaddedHex(val, { byteLength: ctor.BYTES_PER_ELEMENT });
+ };
+
const numberPrefix = printAsFloat ? '' : '0x:';
const printActual = actual.subarray(printElementsStart, printElementsEnd);
- const printExpected: Array<Iterable<string | number>> = [];
+ const printExpected: Array<Iterable<string | number | bigint>> = [];
if (predicatePrinter) {
for (const { leftHeader, getValueForCell: cell } of predicatePrinter) {
printExpected.push(
@@ -246,7 +253,7 @@ function failCheckElements({
const opts = {
fillToWidth: 120,
- numberToString,
+ numericToString,
};
const msg = `Array had unexpected contents at indices ${failedElementsFirst} through ${failedElementsLast}.
Starting at index ${printElementsStart}:
@@ -263,10 +270,11 @@ ${generatePrettyTable(opts, [
// Helper helpers
/** Convert an integral `number` into a hex string, padded to the specified `byteLength`. */
-function intToPaddedHex(number: number, { byteLength }: { byteLength: number }) {
- assert(Number.isInteger(number), 'number must be integer');
- let s = Math.abs(number).toString(16);
- if (byteLength) s = s.padStart(byteLength * 2, '0');
- if (number < 0) s = '-' + s;
- return s;
+function intToPaddedHex(val: number | bigint, { byteLength }: { byteLength: number }) {
+ assert(Number.isInteger(val), 'number must be integer');
+ const is_negative = typeof val === 'number' ? val < 0 : val < 0n;
+ let str = (is_negative ? -val : val).toString(16);
+ if (byteLength) str = str.padStart(byteLength * 2, '0');
+ if (is_negative) str = '-' + str;
+ return str;
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/color_space_conversion.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/color_space_conversion.ts
index a1de0e48ba..4ab3679b23 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/color_space_conversion.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/color_space_conversion.ts
@@ -143,12 +143,7 @@ function XYZ_to_lin_P3(XYZ: Array<Array<number>>) {
* https://drafts.csswg.org/css-color/#predefined-to-predefined
* display-p3 and sRGB share the same white points.
*/
-export function displayP3ToSrgb(pixel: { R: number; G: number; B: number; A: number }): {
- R: number;
- G: number;
- B: number;
- A: number;
-} {
+export function displayP3ToSrgb(pixel: Readonly<RGBA>): RGBA {
assert(
pixel.R !== undefined && pixel.G !== undefined && pixel.B !== undefined,
'color space conversion requires all of R, G and B components'
@@ -161,11 +156,7 @@ export function displayP3ToSrgb(pixel: { R: number; G: number; B: number; A: num
rgbVec = [rgbMatrix[0][0], rgbMatrix[1][0], rgbMatrix[2][0]];
rgbVec = gam_sRGB(rgbVec);
- pixel.R = rgbVec[0];
- pixel.G = rgbVec[1];
- pixel.B = rgbVec[2];
-
- return pixel;
+ return { R: rgbVec[0], G: rgbVec[1], B: rgbVec[2], A: pixel.A };
}
/**
* @returns the converted pixels in `{R: number, G: number, B: number, A: number}`.
@@ -174,12 +165,7 @@ export function displayP3ToSrgb(pixel: { R: number; G: number; B: number; A: num
* https://drafts.csswg.org/css-color/#predefined-to-predefined
* display-p3 and sRGB share the same white points.
*/
-export function srgbToDisplayP3(pixel: { R: number; G: number; B: number; A: number }): {
- R: number;
- G: number;
- B: number;
- A: number;
-} {
+export function srgbToDisplayP3(pixel: Readonly<RGBA>): RGBA {
assert(
pixel.R !== undefined && pixel.G !== undefined && pixel.B !== undefined,
'color space conversion requires all of R, G and B components'
@@ -192,13 +178,10 @@ export function srgbToDisplayP3(pixel: { R: number; G: number; B: number; A: num
rgbVec = [rgbMatrix[0][0], rgbMatrix[1][0], rgbMatrix[2][0]];
rgbVec = gam_P3(rgbVec);
- pixel.R = rgbVec[0];
- pixel.G = rgbVec[1];
- pixel.B = rgbVec[2];
-
- return pixel;
+ return { R: rgbVec[0], G: rgbVec[1], B: rgbVec[2], A: pixel.A };
}
+export type RGBA = { R: number; G: number; B: number; A: number };
type InPlaceColorConversion = (rgba: {
R: number;
G: number;
@@ -247,9 +230,10 @@ export function makeInPlaceColorConversion({
// This technically represents colors outside the src gamut, so no clamping yet.
if (requireColorSpaceConversion) {
- // WebGPU currently only supports dstColorSpace = 'srgb'.
if (srcColorSpace === 'display-p3' && dstColorSpace === 'srgb') {
- rgba = displayP3ToSrgb(rgba);
+ Object.assign(rgba, displayP3ToSrgb(rgba));
+ } else if (srcColorSpace === 'srgb' && dstColorSpace === 'display-p3') {
+ Object.assign(rgba, srgbToDisplayP3(rgba));
} else {
unreachable();
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/compare.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/compare.ts
index 45599d25f6..3aa62d6781 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/compare.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/compare.ts
@@ -5,10 +5,18 @@ import {
deserializeExpectation,
serializeExpectation,
} from '../shader/execution/expression/case_cache.js';
-import { Expectation, toComparator } from '../shader/execution/expression/expression.js';
+import { Expectation, toComparator } from '../shader/execution/expression/expectation.js';
import BinaryStream from './binary_stream.js';
-import { isFloatValue, Matrix, Scalar, Value, Vector } from './conversion.js';
+import {
+ ArrayValue,
+ isFloatValue,
+ isScalarValue,
+ MatrixValue,
+ ScalarValue,
+ Value,
+ VectorValue,
+} from './conversion.js';
import { FPInterval } from './floating_point.js';
/** Comparison describes the result of a Comparator function. */
@@ -98,9 +106,9 @@ function compareValue(got: Value, expected: Value): Comparison {
}
}
- if (got instanceof Scalar) {
+ if (isScalarValue(got)) {
const g = got;
- const e = expected as Scalar;
+ const e = expected as ScalarValue;
const isFloat = g.type.kind === 'f64' || g.type.kind === 'f32' || g.type.kind === 'f16';
const matched =
(isFloat && (g.value as number) === (e.value as number)) || (!isFloat && g.value === e.value);
@@ -111,8 +119,8 @@ function compareValue(got: Value, expected: Value): Comparison {
};
}
- if (got instanceof Vector) {
- const e = expected as Vector;
+ if (got instanceof VectorValue || got instanceof ArrayValue) {
+ const e = expected as VectorValue | ArrayValue;
const gLen = got.elements.length;
const eLen = e.elements.length;
let matched = gLen === eLen;
@@ -130,8 +138,8 @@ function compareValue(got: Value, expected: Value): Comparison {
};
}
- if (got instanceof Matrix) {
- const e = expected as Matrix;
+ if (got instanceof MatrixValue) {
+ const e = expected as MatrixValue;
const gCols = got.type.cols;
const eCols = e.type.cols;
const gRows = got.type.rows;
@@ -153,7 +161,7 @@ function compareValue(got: Value, expected: Value): Comparison {
};
}
- throw new Error(`unhandled type '${typeof got}`);
+ throw new Error(`unhandled type '${typeof got}'`);
}
/**
@@ -175,7 +183,7 @@ function compareInterval(got: Value, expected: FPInterval): Comparison {
}
}
- if (got instanceof Scalar) {
+ if (isScalarValue(got)) {
const g = got.value as number;
const matched = expected.contains(g);
return {
@@ -197,7 +205,7 @@ function compareInterval(got: Value, expected: FPInterval): Comparison {
*/
function compareVector(got: Value, expected: FPInterval[]): Comparison {
// Check got type
- if (!(got instanceof Vector)) {
+ if (!(got instanceof VectorValue)) {
return {
matched: false,
got: `${Colors.red((typeof got).toString())}(${got})`,
@@ -262,7 +270,7 @@ function convertArrayToString<T>(m: T[]): string {
*/
function compareMatrix(got: Value, expected: FPInterval[][]): Comparison {
// Check got type
- if (!(got instanceof Matrix)) {
+ if (!(got instanceof MatrixValue)) {
return {
matched: false,
got: `${Colors.red((typeof got).toString())}(${got})`,
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/constants.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/constants.ts
index 5ee819c64e..11f0677316 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/constants.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/constants.ts
@@ -242,6 +242,21 @@ export const kBit = {
} as const;
export const kValue = {
+ // Limits of i64
+ i64: {
+ positive: {
+ min: BigInt(0n),
+ max: BigInt(9223372036854775807n),
+ },
+ negative: {
+ min: BigInt(-9223372036854775808n),
+ max: BigInt(0n),
+ },
+ isOOB: (val: bigint): boolean => {
+ return val > kValue.i64.positive.max || val < kValue.i64.negative.min;
+ },
+ },
+
// Limits of i32
i32: {
positive: {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/conversion.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/conversion.ts
index d98367447d..29d892d14b 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/conversion.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/conversion.ts
@@ -6,6 +6,7 @@ import { Float16Array } from '../../external/petamoriken/float16/float16.js';
import BinaryStream from './binary_stream.js';
import { kBit } from './constants.js';
import {
+ align,
cartesianProduct,
clamp,
correctlyRoundedF16,
@@ -84,6 +85,8 @@ const workingDataI16 = new Int16Array(workingData);
const workingDataI32 = new Int32Array(workingData);
const workingDataI8 = new Int8Array(workingData);
const workingDataF64 = new Float64Array(workingData);
+const workingDataI64 = new BigInt64Array(workingData);
+const workingDataU64 = new BigUint64Array(workingData);
const workingDataView = new DataView(workingData);
/**
@@ -107,7 +110,7 @@ export function float32ToFloatBits(
assert(mantissaBits <= 23);
if (Number.isNaN(n)) {
- // NaN = all exponent bits true, 1 or more mantissia bits true
+ // NaN = all exponent bits true, 1 or more mantissa bits true
return (((1 << exponentBits) - 1) << mantissaBits) | ((1 << mantissaBits) - 1);
}
@@ -584,6 +587,7 @@ export type ScalarKind =
| 'u32'
| 'u16'
| 'u8'
+ | 'abstract-int'
| 'i32'
| 'i16'
| 'i8'
@@ -593,11 +597,18 @@ export type ScalarKind =
export class ScalarType {
readonly kind: ScalarKind; // The named type
readonly _size: number; // In bytes
- readonly read: (buf: Uint8Array, offset: number) => Scalar; // reads a scalar from a buffer
-
- constructor(kind: ScalarKind, size: number, read: (buf: Uint8Array, offset: number) => Scalar) {
+ readonly _signed: boolean;
+ readonly read: (buf: Uint8Array, offset: number) => ScalarValue; // reads a scalar from a buffer
+
+ constructor(
+ kind: ScalarKind,
+ size: number,
+ signed: boolean,
+ read: (buf: Uint8Array, offset: number) => ScalarValue
+ ) {
this.kind = kind;
this._size = size;
+ this._signed = signed;
this.read = read;
}
@@ -609,32 +620,64 @@ export class ScalarType {
return this._size;
}
- /** Constructs a Scalar of this type with `value` */
- public create(value: number): Scalar {
- switch (this.kind) {
- case 'abstract-float':
- return abstractFloat(value);
- case 'f64':
- return f64(value);
- case 'f32':
- return f32(value);
- case 'f16':
- return f16(value);
- case 'u32':
- return u32(value);
- case 'u16':
- return u16(value);
- case 'u8':
- return u8(value);
- case 'i32':
- return i32(value);
- case 'i16':
- return i16(value);
- case 'i8':
- return i8(value);
- case 'bool':
- return bool(value !== 0);
+ public get alignment(): number {
+ return this._size;
+ }
+
+ public get signed(): boolean {
+ return this._signed;
+ }
+
+ // This allows width to be checked in cases where scalar and vector types are mixed.
+ public get width(): number {
+ return 1;
+ }
+
+ public requiresF16(): boolean {
+ return this.kind === 'f16';
+ }
+
+ /** Constructs a ScalarValue of this type with `value` */
+ public create(value: number | bigint): ScalarValue {
+ switch (typeof value) {
+ case 'number':
+ switch (this.kind) {
+ case 'abstract-float':
+ return abstractFloat(value);
+ case 'abstract-int':
+ return abstractInt(BigInt(value));
+ case 'f64':
+ return f64(value);
+ case 'f32':
+ return f32(value);
+ case 'f16':
+ return f16(value);
+ case 'u32':
+ return u32(value);
+ case 'u16':
+ return u16(value);
+ case 'u8':
+ return u8(value);
+ case 'i32':
+ return i32(value);
+ case 'i16':
+ return i16(value);
+ case 'i8':
+ return i8(value);
+ case 'bool':
+ return bool(value !== 0);
+ }
+ break;
+ case 'bigint':
+ switch (this.kind) {
+ case 'abstract-int':
+ return abstractInt(value);
+ case 'bool':
+ return bool(value !== 0n);
+ }
+ break;
}
+ unreachable(`Scalar<${this.kind}>.create() does not support ${typeof value}`);
}
}
@@ -643,6 +686,20 @@ export class VectorType {
readonly width: number; // Number of elements in the vector
readonly elementType: ScalarType; // Element type
+ // Maps a string representation of a vector type to vector type.
+ private static instances = new Map<string, VectorType>();
+
+ static create(width: number, elementType: ScalarType): VectorType {
+ const key = `${elementType.toString()} ${width}}`;
+ let ty = this.instances.get(key);
+ if (ty !== undefined) {
+ return ty;
+ }
+ ty = new VectorType(width, elementType);
+ this.instances.set(key, ty);
+ return ty;
+ }
+
constructor(width: number, elementType: ScalarType) {
this.width = width;
this.elementType = elementType;
@@ -652,13 +709,13 @@ export class VectorType {
* @returns a vector constructed from the values read from the buffer at the
* given byte offset
*/
- public read(buf: Uint8Array, offset: number): Vector {
- const elements: Array<Scalar> = [];
+ public read(buf: Uint8Array, offset: number): VectorValue {
+ const elements: Array<ScalarValue> = [];
for (let i = 0; i < this.width; i++) {
elements[i] = this.elementType.read(buf, offset);
offset += this.elementType.size;
}
- return new Vector(elements);
+ return new VectorValue(elements);
}
public toString(): string {
@@ -669,29 +726,27 @@ export class VectorType {
return this.elementType.size * this.width;
}
+ public get alignment(): number {
+ return VectorType.alignmentOf(this.width, this.elementType);
+ }
+
+ public static alignmentOf(width: number, elementType: ScalarType) {
+ return elementType.size * (width === 3 ? 4 : width);
+ }
+
/** Constructs a Vector of this type with the given values */
- public create(value: number | readonly number[]): Vector {
+ public create(value: (number | bigint) | readonly (number | bigint)[]): VectorValue {
if (value instanceof Array) {
assert(value.length === this.width);
} else {
value = Array(this.width).fill(value);
}
- return new Vector(value.map(v => this.elementType.create(v)));
+ return new VectorValue(value.map(v => this.elementType.create(v)));
}
-}
-// Maps a string representation of a vector type to vector type.
-const vectorTypes = new Map<string, VectorType>();
-
-export function TypeVec(width: number, elementType: ScalarType): VectorType {
- const key = `${elementType.toString()} ${width}}`;
- let ty = vectorTypes.get(key);
- if (ty !== undefined) {
- return ty;
+ public requiresF16(): boolean {
+ return this.elementType.requiresF16();
}
- ty = new VectorType(width, elementType);
- vectorTypes.set(key, ty);
- return ty;
}
/** MatrixType describes the type of WGSL Matrix. */
@@ -700,6 +755,20 @@ export class MatrixType {
readonly rows: number; // Number of elements per column in the Matrix
readonly elementType: ScalarType; // Element type
+ // Maps a string representation of a Matrix type to Matrix type.
+ private static instances = new Map<string, MatrixType>();
+
+ static create(cols: number, rows: number, elementType: ScalarType): MatrixType {
+ const key = `${elementType.toString()} ${cols} ${rows}`;
+ let ty = this.instances.get(key);
+ if (ty !== undefined) {
+ return ty;
+ }
+ ty = new MatrixType(cols, rows, elementType);
+ this.instances.set(key, ty);
+ return ty;
+ }
+
constructor(cols: number, rows: number, elementType: ScalarType) {
this.cols = cols;
this.rows = rows;
@@ -716,8 +785,8 @@ export class MatrixType {
* @returns a Matrix constructed from the values read from the buffer at the
* given byte offset
*/
- public read(buf: Uint8Array, offset: number): Matrix {
- const elements: Scalar[][] = [...Array(this.cols)].map(_ => [...Array(this.rows)]);
+ public read(buf: Uint8Array, offset: number): MatrixValue {
+ const elements: ScalarValue[][] = [...Array(this.cols)].map(_ => [...Array(this.rows)]);
for (let c = 0; c < this.cols; c++) {
for (let r = 0; r < this.rows; r++) {
elements[c][r] = this.elementType.read(buf, offset);
@@ -729,100 +798,265 @@ export class MatrixType {
offset += this.elementType.size;
}
}
- return new Matrix(elements);
+ return new MatrixValue(elements);
}
public toString(): string {
return `mat${this.cols}x${this.rows}<${this.elementType}>`;
}
+
+ public get size(): number {
+ return VectorType.alignmentOf(this.rows, this.elementType) * this.cols;
+ }
+
+ public get alignment(): number {
+ return VectorType.alignmentOf(this.rows, this.elementType);
+ }
+
+ public requiresF16(): boolean {
+ return this.elementType.requiresF16();
+ }
+
+ /** Constructs a Matrix of this type with the given values */
+ public create(value: (number | bigint) | readonly (number | bigint)[]): MatrixValue {
+ if (value instanceof Array) {
+ assert(value.length === this.cols * this.rows);
+ } else {
+ value = Array(this.cols * this.rows).fill(value);
+ }
+ const columns: (number | bigint)[][] = [];
+ for (let i = 0; i < this.cols; i++) {
+ const start = i * this.rows;
+ columns.push(value.slice(start, start + this.rows));
+ }
+ return new MatrixValue(columns.map(c => c.map(v => this.elementType.create(v))));
+ }
}
-// Maps a string representation of a Matrix type to Matrix type.
-const matrixTypes = new Map<string, MatrixType>();
+/** ArrayType describes the type of WGSL Array. */
+export class ArrayType {
+ readonly count: number; // Number of elements in the array. Zero represents a runtime-sized array.
+ readonly elementType: Type; // Element type
-export function TypeMat(cols: number, rows: number, elementType: ScalarType): MatrixType {
- const key = `${elementType.toString()} ${cols} ${rows}`;
- let ty = matrixTypes.get(key);
- if (ty !== undefined) {
+ // Maps a string representation of a array type to array type.
+ private static instances = new Map<string, ArrayType>();
+
+ static create(count: number, elementType: Type): ArrayType {
+ const key = `${elementType.toString()} ${count}`;
+ let ty = this.instances.get(key);
+ if (ty !== undefined) {
+ return ty;
+ }
+ ty = new ArrayType(count, elementType);
+ this.instances.set(key, ty);
return ty;
}
- ty = new MatrixType(cols, rows, elementType);
- matrixTypes.set(key, ty);
- return ty;
+
+ constructor(count: number, elementType: Type) {
+ this.count = count;
+ this.elementType = elementType;
+ }
+
+ /**
+ * @returns a array constructed from the values read from the buffer at the
+ * given byte offset
+ */
+ public read(buf: Uint8Array, offset: number): ArrayValue {
+ const elements: Array<Value> = [];
+
+ for (let i = 0; i < this.count; i++) {
+ elements[i] = this.elementType.read(buf, offset);
+ offset += this.stride;
+ }
+ return new ArrayValue(elements);
+ }
+
+ public toString(): string {
+ return this.count !== 0
+ ? `array<${this.elementType}, ${this.count}>`
+ : `array<${this.elementType}>`;
+ }
+
+ public get stride(): number {
+ return align(this.elementType.size, this.elementType.alignment);
+ }
+
+ public get size(): number {
+ return this.stride * this.count;
+ }
+
+ public get alignment(): number {
+ return this.elementType.alignment;
+ }
+
+ public requiresF16(): boolean {
+ return this.elementType.requiresF16();
+ }
+
+ /** Constructs an Array of this type with the given values */
+ public create(value: (number | bigint) | readonly (number | bigint)[]): ArrayValue {
+ if (value instanceof Array) {
+ assert(value.length === this.count);
+ } else {
+ value = Array(this.count).fill(value);
+ }
+ return new ArrayValue(value.map(v => this.elementType.create(v)));
+ }
}
-/** Type is a ScalarType, VectorType, or MatrixType. */
-export type Type = ScalarType | VectorType | MatrixType;
+/** ArrayElementType infers the element type of the indexable type A */
+type ArrayElementType<A> = A extends { [index: number]: infer T } ? T : never;
/** Copy bytes from `buf` at `offset` into the working data, then read it out using `workingDataOut` */
-function valueFromBytes(workingDataOut: TypedArrayBufferView, buf: Uint8Array, offset: number) {
+function valueFromBytes<A extends TypedArrayBufferView>(
+ workingDataOut: A,
+ buf: Uint8Array,
+ offset: number
+): ArrayElementType<A> {
for (let i = 0; i < workingDataOut.BYTES_PER_ELEMENT; ++i) {
workingDataU8[i] = buf[offset + i];
}
- return workingDataOut[0];
+ return workingDataOut[0] as ArrayElementType<A>;
}
-export const TypeI32 = new ScalarType('i32', 4, (buf: Uint8Array, offset: number) =>
+const abstractIntType = new ScalarType('abstract-int', 8, true, (buf: Uint8Array, offset: number) =>
+ abstractInt(valueFromBytes(workingDataI64, buf, offset))
+);
+const i32Type = new ScalarType('i32', 4, true, (buf: Uint8Array, offset: number) =>
i32(valueFromBytes(workingDataI32, buf, offset))
);
-export const TypeU32 = new ScalarType('u32', 4, (buf: Uint8Array, offset: number) =>
+const u32Type = new ScalarType('u32', 4, false, (buf: Uint8Array, offset: number) =>
u32(valueFromBytes(workingDataU32, buf, offset))
);
-export const TypeAbstractFloat = new ScalarType(
+const i16Type = new ScalarType('i16', 2, true, (buf: Uint8Array, offset: number) =>
+ i16(valueFromBytes(workingDataI16, buf, offset))
+);
+const u16Type = new ScalarType('u16', 2, false, (buf: Uint8Array, offset: number) =>
+ u16(valueFromBytes(workingDataU16, buf, offset))
+);
+const i8Type = new ScalarType('i8', 1, true, (buf: Uint8Array, offset: number) =>
+ i8(valueFromBytes(workingDataI8, buf, offset))
+);
+const u8Type = new ScalarType('u8', 1, false, (buf: Uint8Array, offset: number) =>
+ u8(valueFromBytes(workingDataU8, buf, offset))
+);
+const abstractFloatType = new ScalarType(
'abstract-float',
8,
+ true,
(buf: Uint8Array, offset: number) => abstractFloat(valueFromBytes(workingDataF64, buf, offset))
);
-export const TypeF64 = new ScalarType('f64', 8, (buf: Uint8Array, offset: number) =>
+const f64Type = new ScalarType('f64', 8, true, (buf: Uint8Array, offset: number) =>
f64(valueFromBytes(workingDataF64, buf, offset))
);
-export const TypeF32 = new ScalarType('f32', 4, (buf: Uint8Array, offset: number) =>
+const f32Type = new ScalarType('f32', 4, true, (buf: Uint8Array, offset: number) =>
f32(valueFromBytes(workingDataF32, buf, offset))
);
-export const TypeI16 = new ScalarType('i16', 2, (buf: Uint8Array, offset: number) =>
- i16(valueFromBytes(workingDataI16, buf, offset))
-);
-export const TypeU16 = new ScalarType('u16', 2, (buf: Uint8Array, offset: number) =>
- u16(valueFromBytes(workingDataU16, buf, offset))
-);
-export const TypeF16 = new ScalarType('f16', 2, (buf: Uint8Array, offset: number) =>
+const f16Type = new ScalarType('f16', 2, true, (buf: Uint8Array, offset: number) =>
f16Bits(valueFromBytes(workingDataU16, buf, offset))
);
-export const TypeI8 = new ScalarType('i8', 1, (buf: Uint8Array, offset: number) =>
- i8(valueFromBytes(workingDataI8, buf, offset))
-);
-export const TypeU8 = new ScalarType('u8', 1, (buf: Uint8Array, offset: number) =>
- u8(valueFromBytes(workingDataU8, buf, offset))
-);
-export const TypeBool = new ScalarType('bool', 4, (buf: Uint8Array, offset: number) =>
+const boolType = new ScalarType('bool', 4, false, (buf: Uint8Array, offset: number) =>
bool(valueFromBytes(workingDataU32, buf, offset) !== 0)
);
+/** Type is a ScalarType, VectorType, MatrixType or ArrayType. */
+export type Type = ScalarType | VectorType | MatrixType | ArrayType;
+
+/** Type holds pre-declared Types along with helper constructor functions. */
+export const Type = {
+ abstractInt: abstractIntType,
+ 'abstract-int': abstractIntType,
+ i32: i32Type,
+ u32: u32Type,
+ i16: i16Type,
+ u16: u16Type,
+ i8: i8Type,
+ u8: u8Type,
+
+ abstractFloat: abstractFloatType,
+ 'abstract-float': abstractFloatType,
+ f64: f64Type,
+ f32: f32Type,
+ f16: f16Type,
+
+ bool: boolType,
+
+ vec: (width: number, elementType: ScalarType) => VectorType.create(width, elementType),
+
+ vec2ai: VectorType.create(2, abstractIntType),
+ vec2i: VectorType.create(2, i32Type),
+ vec2u: VectorType.create(2, u32Type),
+ vec2af: VectorType.create(2, abstractFloatType),
+ vec2f: VectorType.create(2, f32Type),
+ vec2h: VectorType.create(2, f16Type),
+ vec2b: VectorType.create(2, boolType),
+ vec3ai: VectorType.create(3, abstractIntType),
+ vec3i: VectorType.create(3, i32Type),
+ vec3u: VectorType.create(3, u32Type),
+ vec3af: VectorType.create(3, abstractFloatType),
+ vec3f: VectorType.create(3, f32Type),
+ vec3h: VectorType.create(3, f16Type),
+ vec3b: VectorType.create(3, boolType),
+ vec4ai: VectorType.create(4, abstractIntType),
+ vec4i: VectorType.create(4, i32Type),
+ vec4u: VectorType.create(4, u32Type),
+ vec4af: VectorType.create(4, abstractFloatType),
+ vec4f: VectorType.create(4, f32Type),
+ vec4h: VectorType.create(4, f16Type),
+ vec4b: VectorType.create(4, boolType),
+
+ mat: (cols: number, rows: number, elementType: ScalarType) =>
+ MatrixType.create(cols, rows, elementType),
+
+ mat2x2f: MatrixType.create(2, 2, f32Type),
+ mat2x2h: MatrixType.create(2, 2, f16Type),
+ mat3x2f: MatrixType.create(3, 2, f32Type),
+ mat3x2h: MatrixType.create(3, 2, f16Type),
+ mat4x2f: MatrixType.create(4, 2, f32Type),
+ mat4x2h: MatrixType.create(4, 2, f16Type),
+ mat2x3f: MatrixType.create(2, 3, f32Type),
+ mat2x3h: MatrixType.create(2, 3, f16Type),
+ mat3x3f: MatrixType.create(3, 3, f32Type),
+ mat3x3h: MatrixType.create(3, 3, f16Type),
+ mat4x3f: MatrixType.create(4, 3, f32Type),
+ mat4x3h: MatrixType.create(4, 3, f16Type),
+ mat2x4f: MatrixType.create(2, 4, f32Type),
+ mat2x4h: MatrixType.create(2, 4, f16Type),
+ mat3x4f: MatrixType.create(3, 4, f32Type),
+ mat3x4h: MatrixType.create(3, 4, f16Type),
+ mat4x4f: MatrixType.create(4, 4, f32Type),
+ mat4x4h: MatrixType.create(4, 4, f16Type),
+
+ array: (count: number, elementType: Type) => ArrayType.create(count, elementType),
+};
+
/** @returns the ScalarType from the ScalarKind */
export function scalarType(kind: ScalarKind): ScalarType {
switch (kind) {
case 'abstract-float':
- return TypeAbstractFloat;
+ return Type.abstractFloat;
case 'f64':
- return TypeF64;
+ return Type.f64;
case 'f32':
- return TypeF32;
+ return Type.f32;
case 'f16':
- return TypeF16;
+ return Type.f16;
case 'u32':
- return TypeU32;
+ return Type.u32;
case 'u16':
- return TypeU16;
+ return Type.u16;
case 'u8':
- return TypeU8;
+ return Type.u8;
+ case 'abstract-int':
+ return Type.abstractInt;
case 'i32':
- return TypeI32;
+ return Type.i32;
case 'i16':
- return TypeI16;
+ return Type.i16;
case 'i8':
- return TypeI8;
+ return Type.i8;
case 'bool':
- return TypeBool;
+ return Type.bool;
}
}
@@ -837,23 +1071,54 @@ export function numElementsOf(ty: Type): number {
if (ty instanceof MatrixType) {
return ty.cols * ty.rows;
}
+ if (ty instanceof ArrayType) {
+ return ty.count;
+ }
throw new Error(`unhandled type ${ty}`);
}
/** @returns the scalar elements of the given Value */
-export function elementsOf(value: Value): Scalar[] {
- if (value instanceof Scalar) {
+export function elementsOf(value: Value): Value[] {
+ if (isScalarValue(value)) {
+ return [value];
+ }
+ if (value instanceof VectorValue) {
+ return value.elements;
+ }
+ if (value instanceof MatrixValue) {
+ return value.elements.flat();
+ }
+ if (value instanceof ArrayValue) {
+ return value.elements;
+ }
+ throw new Error(`unhandled value ${value}`);
+}
+
+/** @returns the scalar elements of the given Value */
+export function scalarElementsOf(value: Value): ScalarValue[] {
+ if (isScalarValue(value)) {
return [value];
}
- if (value instanceof Vector) {
+ if (value instanceof VectorValue) {
return value.elements;
}
- if (value instanceof Matrix) {
+ if (value instanceof MatrixValue) {
return value.elements.flat();
}
+ if (value instanceof ArrayValue) {
+ return value.elements.map(els => scalarElementsOf(els)).flat();
+ }
throw new Error(`unhandled value ${value}`);
}
+/** @returns the inner element type of the given type */
+export function elementTypeOf(t: Type) {
+ if (t instanceof ScalarType) {
+ return t;
+ }
+ return t.elementType;
+}
+
/** @returns the scalar (element) type of the given Type */
export function scalarTypeOf(ty: Type): ScalarType {
if (ty instanceof ScalarType) {
@@ -865,27 +1130,93 @@ export function scalarTypeOf(ty: Type): ScalarType {
if (ty instanceof MatrixType) {
return ty.elementType;
}
+ if (ty instanceof ArrayType) {
+ return scalarTypeOf(ty.elementType);
+ }
throw new Error(`unhandled type ${ty}`);
}
-/** ScalarValue is the JS type that can be held by a Scalar */
-type ScalarValue = boolean | number;
+/**
+ * @returns the implicit concretized type of the given Type.
+ * @param abstractIntToF32 if true, returns f32 for abstractInt else i32
+ * Example: vec3<abstract-float> -> vec3<float>
+ */
+export function concreteTypeOf(ty: Type, allowedScalarTypes?: Type[]): Type {
+ if (allowedScalarTypes && allowedScalarTypes.length > 0) {
+ // https://www.w3.org/TR/WGSL/#conversion-rank
+ switch (ty) {
+ case Type.abstractInt:
+ if (allowedScalarTypes.includes(Type.i32)) {
+ return Type.i32;
+ }
+ if (allowedScalarTypes.includes(Type.u32)) {
+ return Type.u32;
+ }
+ // fallthrough.
+ case Type.abstractFloat:
+ if (allowedScalarTypes.includes(Type.f32)) {
+ return Type.f32;
+ }
+ if (allowedScalarTypes.includes(Type.f16)) {
+ return Type.f32;
+ }
+ throw new Error(`no ${ty}`);
+ }
+ } else {
+ switch (ty) {
+ case Type.abstractInt:
+ return Type.i32;
+ case Type.abstractFloat:
+ return Type.f32;
+ }
+ }
+ if (ty instanceof ScalarType) {
+ return ty;
+ }
+ if (ty instanceof VectorType) {
+ return Type.vec(ty.width, concreteTypeOf(ty.elementType, allowedScalarTypes) as ScalarType);
+ }
+ if (ty instanceof MatrixType) {
+ return Type.mat(
+ ty.cols,
+ ty.rows,
+ concreteTypeOf(ty.elementType, allowedScalarTypes) as ScalarType
+ );
+ }
+ if (ty instanceof ArrayType) {
+ return Type.array(ty.count, concreteTypeOf(ty.elementType, allowedScalarTypes));
+ }
+ throw new Error(`unhandled type ${ty}`);
+}
-/** Class that encapsulates a single scalar value of various types. */
-export class Scalar {
- readonly value: ScalarValue; // The scalar value
- readonly type: ScalarType; // The type of the scalar
+function hex(sizeInBytes: number, bitsLow: number, bitsHigh?: number) {
+ let hex = '';
+ workingDataU32[0] = bitsLow;
+ if (bitsHigh !== undefined) {
+ workingDataU32[1] = bitsHigh;
+ }
+ for (let i = 0; i < sizeInBytes; ++i) {
+ hex = workingDataU8[i].toString(16).padStart(2, '0') + hex;
+ }
+ return `0x${hex}`;
+}
+
+function withPoint(x: number) {
+ const str = `${x}`;
+ return str.indexOf('.') > 0 || str.indexOf('e') > 0 ? str : `${str}.0`;
+}
- // The scalar value, packed in one or two 32-bit unsigned integers.
- // Whether or not the bits1 is used depends on `this.type.size`.
- readonly bits1: number;
- readonly bits0: number;
+/** Class that encapsulates a single abstract-int value. */
+export class AbstractIntValue {
+ readonly value: bigint; // The abstract-integer value
+ readonly bitsLow: number; // The low 32 bits of the abstract-integer value.
+ readonly bitsHigh: number; // The high 32 bits of the abstract-integer value.
+ readonly type = Type.abstractInt; // The type of the value.
- public constructor(type: ScalarType, value: ScalarValue, bits1: number, bits0: number) {
+ public constructor(value: bigint, bitsLow: number, bitsHigh: number) {
this.value = value;
- this.type = type;
- this.bits1 = bits1;
- this.bits0 = bits0;
+ this.bitsLow = bitsLow;
+ this.bitsHigh = bitsHigh;
}
/**
@@ -894,200 +1225,583 @@ export class Scalar {
* @param offset the offset in buffer, in units of `buffer`
*/
public copyTo(buffer: TypedArrayBufferView, offset: number) {
- assert(this.type.kind !== 'f64', `Copying f64 values to/from buffers is not defined`);
- workingDataU32[1] = this.bits1;
- workingDataU32[0] = this.bits0;
- for (let i = 0; i < this.type.size; i++) {
+ workingDataU32[0] = this.bitsLow;
+ workingDataU32[1] = this.bitsHigh;
+ for (let i = 0; i < 8; i++) {
buffer[offset + i] = workingDataU8[i];
}
}
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ // WGSL parses negative numbers as a negated positive.
+ // This means '-9223372036854775808' parses as `-' & '9223372036854775808', so must be written as
+ // '(-9223372036854775807 - 1)' in WGSL, because '9223372036854775808' is not a valid AbstractInt.
+ if (this.value === -9223372036854775808n) {
+ return `(-9223372036854775807 - 1)`;
+ }
+ return `${this.value}`;
+ }
+
+ public toString(): string {
+ return `${Colors.bold(this.value.toString())} (${hex(8, this.bitsLow, this.bitsHigh)})`;
+ }
+}
+
+/** Class that encapsulates a single abstract-float value. */
+export class AbstractFloatValue {
+ readonly value: number; // The f32 value
+ readonly bitsLow: number; // The low 32 bits of the abstract-float value.
+ readonly bitsHigh: number; // The high 32 bits of the abstract-float value.
+ readonly type = Type.abstractFloat; // The type of the value.
+
+ public constructor(value: number, bitsLow: number, bitsHigh: number) {
+ this.value = value;
+ this.bitsLow = bitsLow;
+ this.bitsHigh = bitsHigh;
+ }
+
/**
- * @returns the WGSL representation of this scalar value
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
*/
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU32[0] = this.bitsLow;
+ workingDataU32[1] = this.bitsHigh;
+ for (let i = 0; i < 8; i++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /** @returns the WGSL representation of this scalar value */
public wgsl(): string {
- const withPoint = (x: number) => {
- const str = `${x}`;
- return str.indexOf('.') > 0 || str.indexOf('e') > 0 ? str : `${str}.0`;
- };
- if (isFinite(this.value as number)) {
- switch (this.type.kind) {
- case 'abstract-float':
- return `${withPoint(this.value as number)}`;
- case 'f64':
- return `${withPoint(this.value as number)}`;
- case 'f32':
- return `${withPoint(this.value as number)}f`;
- case 'f16':
- return `${withPoint(this.value as number)}h`;
- case 'u32':
- return `${this.value}u`;
- case 'i32':
- return `i32(${this.value})`;
- case 'bool':
- return `${this.value}`;
+ return `${withPoint(this.value)}`;
+ }
+
+ public toString(): string {
+ switch (this.value) {
+ case Infinity:
+ case -Infinity:
+ return Colors.bold(this.value.toString());
+ default: {
+ let str = this.value.toString();
+ str = str.indexOf('.') > 0 || str.indexOf('e') > 0 ? str : `${str}.0`;
+ return isSubnormalNumberF64(this.value.valueOf())
+ ? `${Colors.bold(str)} (${hex(8, this.bitsLow, this.bitsHigh)} subnormal)`
+ : `${Colors.bold(str)} (${hex(8, this.bitsLow, this.bitsHigh)})`;
}
}
- throw new Error(
- `scalar of value ${this.value} and type ${this.type} has no WGSL representation`
- );
+ }
+}
+
+/** Class that encapsulates a single i32 value. */
+export class I32Value {
+ readonly value: number; // The i32 value
+ readonly bits: number; // The i32 value, bitcast to a 32-bit integer.
+ readonly type = Type.i32; // The type of the value.
+
+ public constructor(value: number, bits: number) {
+ this.value = value;
+ this.bits = bits;
+ }
+
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU32[0] = this.bits;
+ for (let i = 0; i < 4; i++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ return `i32(${this.value})`;
}
public toString(): string {
- if (this.type.kind === 'bool') {
- return Colors.bold(this.value.toString());
+ return `${Colors.bold(this.value.toString())} (${hex(4, this.bits)})`;
+ }
+}
+
+/** Class that encapsulates a single u32 value. */
+export class U32Value {
+ readonly value: number; // The u32 value
+ readonly type = Type.u32; // The type of the value.
+
+ public constructor(value: number) {
+ this.value = value;
+ }
+
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU32[0] = this.value;
+ for (let i = 0; i < 4; i++) {
+ buffer[offset + i] = workingDataU8[i];
}
+ }
+
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ return `${this.value}u`;
+ }
+
+ public toString(): string {
+ return `${Colors.bold(this.value.toString())} (${hex(4, this.value)})`;
+ }
+}
+
+/**
+ * Class that encapsulates a single i16 value.
+ * @note type does not exist in WGSL yet
+ */
+export class I16Value {
+ readonly value: number; // The i16 value
+ readonly bits: number; // The i16 value, bitcast to a 16-bit integer.
+ readonly type = Type.i16; // The type of the value.
+
+ public constructor(value: number, bits: number) {
+ this.value = value;
+ this.bits = bits;
+ }
+
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU16[0] = this.bits;
+ for (let i = 0; i < 4; i++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ return `i16(${this.value})`;
+ }
+
+ public toString(): string {
+ return `${Colors.bold(this.value.toString())} (${hex(2, this.bits)})`;
+ }
+}
+
+/**
+ * Class that encapsulates a single u16 value.
+ * @note type does not exist in WGSL yet
+ */
+export class U16Value {
+ readonly value: number; // The u16 value
+ readonly type = Type.u16; // The type of the value.
+
+ public constructor(value: number) {
+ this.value = value;
+ }
+
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU16[0] = this.value;
+ for (let i = 0; i < 2; i++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ assert(false, 'u16 is not a WGSL type');
+ return `u16(${this.value})`;
+ }
+
+ public toString(): string {
+ return `${Colors.bold(this.value.toString())} (${hex(2, this.value)})`;
+ }
+}
+
+/**
+ * Class that encapsulates a single i8 value.
+ * @note type does not exist in WGSL yet
+ */
+export class I8Value {
+ readonly value: number; // The i8 value
+ readonly bits: number; // The i8 value, bitcast to a 8-bit integer.
+ readonly type = Type.i8; // The type of the value.
+
+ public constructor(value: number, bits: number) {
+ this.value = value;
+ this.bits = bits;
+ }
+
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU8[0] = this.bits;
+ for (let i = 0; i < 4; i++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ return `i8(${this.value})`;
+ }
+
+ public toString(): string {
+ return `${Colors.bold(this.value.toString())} (${hex(2, this.bits)})`;
+ }
+}
+
+/**
+ * Class that encapsulates a single u8 value.
+ * @note type does not exist in WGSL yet
+ */
+export class U8Value {
+ readonly value: number; // The u8 value
+ readonly type = Type.u8; // The type of the value.
+
+ public constructor(value: number) {
+ this.value = value;
+ }
+
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU8[0] = this.value;
+ for (let i = 0; i < 2; i++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ assert(false, 'u8 is not a WGSL type');
+ return `u8(${this.value})`;
+ }
+
+ public toString(): string {
+ return `${Colors.bold(this.value.toString())} (${hex(2, this.value)})`;
+ }
+}
+
+/**
+ * Class that encapsulates a single f64 value
+ * @note type does not exist in WGSL yet
+ */
+export class F64Value {
+ readonly value: number; // The f32 value
+ readonly bitsLow: number; // The low 32 bits of the abstract-float value.
+ readonly bitsHigh: number; // The high 32 bits of the abstract-float value.
+ readonly type = Type.f64; // The type of the value.
+
+ public constructor(value: number, bitsLow: number, bitsHigh: number) {
+ this.value = value;
+ this.bitsLow = bitsLow;
+ this.bitsHigh = bitsHigh;
+ }
+
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU32[0] = this.bitsLow;
+ workingDataU32[1] = this.bitsHigh;
+ for (let i = 0; i < 8; i++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ assert(false, 'f64 is not a WGSL type');
+ return `${withPoint(this.value)}`;
+ }
+
+ public toString(): string {
switch (this.value) {
case Infinity:
case -Infinity:
return Colors.bold(this.value.toString());
default: {
- workingDataU32[1] = this.bits1;
- workingDataU32[0] = this.bits0;
- let hex = '';
- for (let i = 0; i < this.type.size; ++i) {
- hex = workingDataU8[i].toString(16).padStart(2, '0') + hex;
- }
- const n = this.value as Number;
- if (n !== null && isFloatValue(this)) {
- let str = this.value.toString();
- str = str.indexOf('.') > 0 || str.indexOf('e') > 0 ? str : `${str}.0`;
- switch (this.type.kind) {
- case 'abstract-float':
- return isSubnormalNumberF64(n.valueOf())
- ? `${Colors.bold(str)} (0x${hex} subnormal)`
- : `${Colors.bold(str)} (0x${hex})`;
- case 'f64':
- return isSubnormalNumberF64(n.valueOf())
- ? `${Colors.bold(str)} (0x${hex} subnormal)`
- : `${Colors.bold(str)} (0x${hex})`;
- case 'f32':
- return isSubnormalNumberF32(n.valueOf())
- ? `${Colors.bold(str)} (0x${hex} subnormal)`
- : `${Colors.bold(str)} (0x${hex})`;
- case 'f16':
- return isSubnormalNumberF16(n.valueOf())
- ? `${Colors.bold(str)} (0x${hex} subnormal)`
- : `${Colors.bold(str)} (0x${hex})`;
- default:
- unreachable(
- `Printing of floating point kind ${this.type.kind} is not implemented...`
- );
- }
- }
- return `${Colors.bold(this.value.toString())} (0x${hex})`;
+ let str = this.value.toString();
+ str = str.indexOf('.') > 0 || str.indexOf('e') > 0 ? str : `${str}.0`;
+ return isSubnormalNumberF64(this.value.valueOf())
+ ? `${Colors.bold(str)} (${hex(8, this.bitsLow, this.bitsHigh)} subnormal)`
+ : `${Colors.bold(str)} (${hex(8, this.bitsLow, this.bitsHigh)})`;
}
}
}
}
-export interface ScalarBuilder {
- (value: number): Scalar;
+/** Class that encapsulates a single f32 value. */
+export class F32Value {
+ readonly value: number; // The f32 value
+ readonly bits: number; // The f32 value, bitcast to a 32-bit integer.
+ readonly type = Type.f32; // The type of the value.
+
+ public constructor(value: number, bits: number) {
+ this.value = value;
+ this.bits = bits;
+ }
+
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU32[0] = this.bits;
+ for (let i = 0; i < 4; i++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ return `${withPoint(this.value)}f`;
+ }
+
+ public toString(): string {
+ switch (this.value) {
+ case Infinity:
+ case -Infinity:
+ return Colors.bold(this.value.toString());
+ default: {
+ let str = this.value.toString();
+ str = str.indexOf('.') > 0 || str.indexOf('e') > 0 ? str : `${str}.0`;
+ return isSubnormalNumberF32(this.value.valueOf())
+ ? `${Colors.bold(str)} (${hex(4, this.bits)} subnormal)`
+ : `${Colors.bold(str)} (${hex(4, this.bits)})`;
+ }
+ }
+ }
}
-/** Create a Scalar of `type` by storing `value` as an element of `workingDataArray` and retrieving it.
- * The working data array *must* be an alias of `workingData`.
- */
-function scalarFromValue(
- type: ScalarType,
- workingDataArray: TypedArrayBufferView,
- value: number
-): Scalar {
- // Clear all bits of the working data since `value` may be smaller; the upper bits should be 0.
- workingDataU32[1] = 0;
- workingDataU32[0] = 0;
- workingDataArray[0] = value;
- return new Scalar(type, workingDataArray[0], workingDataU32[1], workingDataU32[0]);
-}
-
-/** Create a Scalar of `type` by storing `value` as an element of `workingDataStoreArray` and
- * reinterpreting it as an element of `workingDataLoadArray`.
- * Both working data arrays *must* be aliases of `workingData`.
- */
-function scalarFromBits(
- type: ScalarType,
- workingDataStoreArray: TypedArrayBufferView,
- workingDataLoadArray: TypedArrayBufferView,
- bits: number
-): Scalar {
- // Clear all bits of the working data since `value` may be smaller; the upper bits should be 0.
- workingDataU32[1] = 0;
- workingDataU32[0] = 0;
- workingDataStoreArray[0] = bits;
- return new Scalar(type, workingDataLoadArray[0], workingDataU32[1], workingDataU32[0]);
+/** Class that encapsulates a single f16 value. */
+export class F16Value {
+ readonly value: number; // The f16 value
+ readonly bits: number; // The f16 value, bitcast to a 16-bit integer.
+ readonly type = Type.f16; // The type of the value.
+
+ public constructor(value: number, bits: number) {
+ this.value = value;
+ this.bits = bits;
+ }
+
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ workingDataU16[0] = this.bits;
+ for (let i = 0; i < 2; i++) {
+ buffer[offset + i] = workingDataU8[i];
+ }
+ }
+
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ return `${withPoint(this.value)}h`;
+ }
+
+ public toString(): string {
+ switch (this.value) {
+ case Infinity:
+ case -Infinity:
+ return Colors.bold(this.value.toString());
+ default: {
+ let str = this.value.toString();
+ str = str.indexOf('.') > 0 || str.indexOf('e') > 0 ? str : `${str}.0`;
+ return isSubnormalNumberF16(this.value.valueOf())
+ ? `${Colors.bold(str)} (${hex(2, this.bits)} subnormal)`
+ : `${Colors.bold(str)} (${hex(2, this.bits)})`;
+ }
+ }
+ }
}
+/** Class that encapsulates a single bool value. */
+export class BoolValue {
+ readonly value: boolean; // The bool value
+ readonly type = Type.bool; // The type of the value.
-/** Create an AbstractFloat from a numeric value, a JS `number`. */
-export const abstractFloat = (value: number): Scalar =>
- scalarFromValue(TypeAbstractFloat, workingDataF64, value);
+ public constructor(value: boolean) {
+ this.value = value;
+ }
-/** Create an f64 from a numeric value, a JS `number`. */
-export const f64 = (value: number): Scalar => scalarFromValue(TypeF64, workingDataF64, value);
+ /**
+ * Copies the scalar value to the buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the offset in buffer, in units of `buffer`
+ */
+ public copyTo(buffer: TypedArrayBufferView, offset: number) {
+ buffer[offset] = this.value ? 1 : 0;
+ }
-/** Create an f32 from a numeric value, a JS `number`. */
-export const f32 = (value: number): Scalar => scalarFromValue(TypeF32, workingDataF32, value);
+ /** @returns the WGSL representation of this scalar value */
+ public wgsl(): string {
+ return this.value.toString();
+ }
-/** Create an f16 from a numeric value, a JS `number`. */
-export const f16 = (value: number): Scalar => scalarFromValue(TypeF16, workingDataF16, value);
+ public toString(): string {
+ return Colors.bold(this.value.toString());
+ }
+}
-/** Create an f32 from a bit representation, a uint32 represented as a JS `number`. */
-export const f32Bits = (bits: number): Scalar =>
- scalarFromBits(TypeF32, workingDataU32, workingDataF32, bits);
+/** Scalar represents all the scalar value types */
+export type ScalarValue =
+ | AbstractIntValue
+ | AbstractFloatValue
+ | I32Value
+ | U32Value
+ | I16Value
+ | U16Value
+ | I8Value
+ | U8Value
+ | F64Value
+ | F32Value
+ | F16Value
+ | BoolValue;
+
+export interface ScalarBuilder<T> {
+ (value: T): ScalarValue;
+}
-/** Create an f16 from a bit representation, a uint16 represented as a JS `number`. */
-export const f16Bits = (bits: number): Scalar =>
- scalarFromBits(TypeF16, workingDataU16, workingDataF16, bits);
+export function isScalarValue(value: object): value is ScalarValue {
+ return (
+ value instanceof AbstractIntValue ||
+ value instanceof AbstractFloatValue ||
+ value instanceof I32Value ||
+ value instanceof U32Value ||
+ value instanceof I16Value ||
+ value instanceof U16Value ||
+ value instanceof I8Value ||
+ value instanceof U8Value ||
+ value instanceof F64Value ||
+ value instanceof F32Value ||
+ value instanceof F16Value ||
+ value instanceof BoolValue
+ );
+}
-/** Create an i32 from a numeric value, a JS `number`. */
-export const i32 = (value: number): Scalar => scalarFromValue(TypeI32, workingDataI32, value);
+/** Create an AbstractInt from a numeric value, a JS `bigint`. */
+export function abstractInt(value: bigint) {
+ workingDataI64[0] = value;
+ return new AbstractIntValue(workingDataI64[0], workingDataU32[0], workingDataU32[1]);
+}
-/** Create an i16 from a numeric value, a JS `number`. */
-export const i16 = (value: number): Scalar => scalarFromValue(TypeI16, workingDataI16, value);
+/** Create an AbstractInt from a bit representation, a uint64 represented as a JS `bigint`. */
+export function abstractIntBits(value: bigint) {
+ workingDataU64[0] = value;
+ return new AbstractIntValue(workingDataI64[0], workingDataU32[0], workingDataU32[1]);
+}
-/** Create an i8 from a numeric value, a JS `number`. */
-export const i8 = (value: number): Scalar => scalarFromValue(TypeI8, workingDataI8, value);
+/** Create an AbstractFloat from a numeric value, a JS `number`. */
+export function abstractFloat(value: number) {
+ workingDataF64[0] = value;
+ return new AbstractFloatValue(workingDataF64[0], workingDataU32[0], workingDataU32[1]);
+}
+
+/** Create an i32 from a numeric value, a JS `number`. */
+export function i32(value: number) {
+ workingDataI32[0] = value;
+ return new I32Value(workingDataI32[0], workingDataU32[0]);
+}
/** Create an i32 from a bit representation, a uint32 represented as a JS `number`. */
-export const i32Bits = (bits: number): Scalar =>
- scalarFromBits(TypeI32, workingDataU32, workingDataI32, bits);
+export function i32Bits(bits: number) {
+ workingDataU32[0] = bits;
+ return new I32Value(workingDataI32[0], workingDataU32[0]);
+}
-/** Create an i16 from a bit representation, a uint16 represented as a JS `number`. */
-export const i16Bits = (bits: number): Scalar =>
- scalarFromBits(TypeI16, workingDataU16, workingDataI16, bits);
+/** Create a u32 from a numeric value, a JS `number`. */
+export function u32(value: number) {
+ workingDataU32[0] = value;
+ return new U32Value(workingDataU32[0]);
+}
-/** Create an i8 from a bit representation, a uint8 represented as a JS `number`. */
-export const i8Bits = (bits: number): Scalar =>
- scalarFromBits(TypeI8, workingDataU8, workingDataI8, bits);
+/** Create a u32 from a bit representation, a uint32 represented as a JS `number`. */
+export function u32Bits(bits: number) {
+ workingDataU32[0] = bits;
+ return new U32Value(workingDataU32[0]);
+}
-/** Create a u32 from a numeric value, a JS `number`. */
-export const u32 = (value: number): Scalar => scalarFromValue(TypeU32, workingDataU32, value);
+/** Create an i16 from a numeric value, a JS `number`. */
+export function i16(value: number) {
+ workingDataI16[0] = value;
+ return new I16Value(workingDataI16[0], workingDataU16[0]);
+}
/** Create a u16 from a numeric value, a JS `number`. */
-export const u16 = (value: number): Scalar => scalarFromValue(TypeU16, workingDataU16, value);
+export function u16(value: number) {
+ workingDataU16[0] = value;
+ return new U16Value(workingDataU16[0]);
+}
+
+/** Create an i8 from a numeric value, a JS `number`. */
+export function i8(value: number) {
+ workingDataI8[0] = value;
+ return new I8Value(workingDataI8[0], workingDataU8[0]);
+}
/** Create a u8 from a numeric value, a JS `number`. */
-export const u8 = (value: number): Scalar => scalarFromValue(TypeU8, workingDataU8, value);
+export function u8(value: number) {
+ workingDataU8[0] = value;
+ return new U8Value(workingDataU8[0]);
+}
+
+/** Create an f64 from a numeric value, a JS `number`. */
+export function f64(value: number) {
+ workingDataF64[0] = value;
+ return new F64Value(workingDataF64[0], workingDataU32[0], workingDataU32[1]);
+}
-/** Create an u32 from a bit representation, a uint32 represented as a JS `number`. */
-export const u32Bits = (bits: number): Scalar =>
- scalarFromBits(TypeU32, workingDataU32, workingDataU32, bits);
+/** Create an f32 from a numeric value, a JS `number`. */
+export function f32(value: number) {
+ workingDataF32[0] = value;
+ return new F32Value(workingDataF32[0], workingDataU32[0]);
+}
+
+/** Create an f32 from a bit representation, a uint32 represented as a JS `number`. */
+export function f32Bits(bits: number) {
+ workingDataU32[0] = bits;
+ return new F32Value(workingDataF32[0], workingDataU32[0]);
+}
-/** Create an u16 from a bit representation, a uint16 represented as a JS `number`. */
-export const u16Bits = (bits: number): Scalar =>
- scalarFromBits(TypeU16, workingDataU16, workingDataU16, bits);
+/** Create an f16 from a numeric value, a JS `number`. */
+export function f16(value: number) {
+ workingDataF16[0] = value;
+ return new F16Value(workingDataF16[0], workingDataU16[0]);
+}
-/** Create an u8 from a bit representation, a uint8 represented as a JS `number`. */
-export const u8Bits = (bits: number): Scalar =>
- scalarFromBits(TypeU8, workingDataU8, workingDataU8, bits);
+/** Create an f16 from a bit representation, a uint16 represented as a JS `number`. */
+export function f16Bits(bits: number) {
+ workingDataU16[0] = bits;
+ return new F16Value(workingDataF16[0], workingDataU16[0]);
+}
/** Create a boolean value. */
-export function bool(value: boolean): Scalar {
- // WGSL does not support using 'bool' types directly in storage / uniform
- // buffers, so instead we pack booleans in a u32, where 'false' is zero and
- // 'true' is any non-zero value.
- workingDataU32[0] = value ? 1 : 0;
- workingDataU32[1] = 0;
- return new Scalar(TypeBool, value, workingDataU32[1], workingDataU32[0]);
+export function bool(value: boolean): ScalarValue {
+ return new BoolValue(value);
}
/** A 'true' literal value */
@@ -1099,11 +1813,11 @@ export const False = bool(false);
/**
* Class that encapsulates a vector value.
*/
-export class Vector {
- readonly elements: Array<Scalar>;
+export class VectorValue {
+ readonly elements: Array<ScalarValue>;
readonly type: VectorType;
- public constructor(elements: Array<Scalar>) {
+ public constructor(elements: Array<ScalarValue>) {
if (elements.length < 2 || elements.length > 4) {
throw new Error(`vector element count must be between 2 and 4, got ${elements.length}`);
}
@@ -1117,7 +1831,7 @@ export class Vector {
}
}
this.elements = elements;
- this.type = TypeVec(elements.length, elements[0].type);
+ this.type = VectorType.create(elements.length, elements[0].type);
}
/**
@@ -1165,19 +1879,24 @@ export class Vector {
}
}
+/** Helper for constructing a new vector with the provided values */
+export function vec(...elements: ScalarValue[]) {
+ return new VectorValue(elements);
+}
+
/** Helper for constructing a new two-element vector with the provided values */
-export function vec2(x: Scalar, y: Scalar) {
- return new Vector([x, y]);
+export function vec2(x: ScalarValue, y: ScalarValue) {
+ return new VectorValue([x, y]);
}
/** Helper for constructing a new three-element vector with the provided values */
-export function vec3(x: Scalar, y: Scalar, z: Scalar) {
- return new Vector([x, y, z]);
+export function vec3(x: ScalarValue, y: ScalarValue, z: ScalarValue) {
+ return new VectorValue([x, y, z]);
}
/** Helper for constructing a new four-element vector with the provided values */
-export function vec4(x: Scalar, y: Scalar, z: Scalar, w: Scalar) {
- return new Vector([x, y, z, w]);
+export function vec4(x: ScalarValue, y: ScalarValue, z: ScalarValue, w: ScalarValue) {
+ return new VectorValue([x, y, z, w]);
}
/**
@@ -1186,7 +1905,7 @@ export function vec4(x: Scalar, y: Scalar, z: Scalar, w: Scalar) {
* @param v array of numbers to be converted, must contain 2, 3 or 4 elements
* @param op function to convert from number to Scalar, e.g. 'f32`
*/
-export function toVector(v: readonly number[], op: (n: number) => Scalar): Vector {
+export function toVector(v: readonly number[], op: (n: number) => ScalarValue): VectorValue {
switch (v.length) {
case 2:
return vec2(op(v[0]), op(v[1]));
@@ -1201,11 +1920,11 @@ export function toVector(v: readonly number[], op: (n: number) => Scalar): Vecto
/**
* Class that encapsulates a Matrix value.
*/
-export class Matrix {
- readonly elements: Scalar[][];
+export class MatrixValue {
+ readonly elements: ScalarValue[][];
readonly type: MatrixType;
- public constructor(elements: Array<Array<Scalar>>) {
+ public constructor(elements: Array<Array<ScalarValue>>) {
const num_cols = elements.length;
if (num_cols < 2 || num_cols > 4) {
throw new Error(`matrix cols count must be between 2 and 4, got ${num_cols}`);
@@ -1226,7 +1945,7 @@ export class Matrix {
}
this.elements = elements;
- this.type = TypeMat(num_cols, num_rows, elem_type);
+ this.type = MatrixType.create(num_cols, num_rows, elem_type);
}
/**
@@ -1262,41 +1981,90 @@ export class Matrix {
}
/**
+ * Class that encapsulates an Array value.
+ */
+export class ArrayValue {
+ readonly elements: Value[];
+ readonly type: ArrayType;
+
+ public constructor(elements: Array<Value>) {
+ const elem_type = elements[0].type;
+ if (!elements.every(c => elements.every(r => objectEquals(r.type, elem_type)))) {
+ throw new Error(`cannot mix array element types`);
+ }
+
+ this.elements = elements;
+ this.type = ArrayType.create(elements.length, elem_type);
+ }
+
+ /**
+ * Copies the array value to the Uint8Array buffer at the provided byte offset.
+ * @param buffer the destination buffer
+ * @param offset the byte offset within buffer
+ */
+ public copyTo(buffer: Uint8Array, offset: number) {
+ for (const element of this.elements) {
+ element.copyTo(buffer, offset);
+ offset += this.type.elementType.size;
+ }
+ }
+
+ /**
+ * @returns the WGSL representation of this array value
+ */
+ public wgsl(): string {
+ const els = this.elements.map(r => r.wgsl()).join(', ');
+ return isAbstractType(this.type.elementType) ? `array(${els})` : `${this.type}(${els})`;
+ }
+
+ public toString(): string {
+ return this.wgsl();
+ }
+}
+
+/** Helper for constructing an ArrayValue with the provided values */
+export function array(...elements: Value[]) {
+ return new ArrayValue(elements);
+}
+
+/**
* Helper for constructing Matrices from arrays of numbers
*
* @param m array of array of numbers to be converted, all Array of number must
* be of the same length. All Arrays must have 2, 3, or 4 elements.
* @param op function to convert from number to Scalar, e.g. 'f32`
*/
-export function toMatrix(m: ROArrayArray<number>, op: (n: number) => Scalar): Matrix {
+export function toMatrix(m: ROArrayArray<number>, op: (n: number) => ScalarValue): MatrixValue {
const cols = m.length;
const rows = m[0].length;
- const elements: Scalar[][] = [...Array<Scalar[]>(cols)].map(_ => [...Array<Scalar>(rows)]);
+ const elements: ScalarValue[][] = [...Array<ScalarValue[]>(cols)].map(_ => [
+ ...Array<ScalarValue>(rows),
+ ]);
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
elements[i][j] = op(m[i][j]);
}
}
- return new Matrix(elements);
+ return new MatrixValue(elements);
}
-/** Value is a Scalar or Vector value. */
-export type Value = Scalar | Vector | Matrix;
+/** Value is a Scalar, Vector, Matrix or Array value. */
+export type Value = ScalarValue | VectorValue | MatrixValue | ArrayValue;
-export type SerializedValueScalar = {
+export type SerializedScalarValue = {
kind: 'scalar';
type: ScalarKind;
value: boolean | number;
};
-export type SerializedValueVector = {
+export type SerializedVectorValue = {
kind: 'vector';
type: ScalarKind;
value: boolean[] | readonly number[];
};
-export type SerializedValueMatrix = {
+export type SerializedMatrixValue = {
kind: 'matrix';
type: ScalarKind;
value: ROArrayArray<number>;
@@ -1314,6 +2082,7 @@ enum SerializedScalarKind {
I16,
I8,
Bool,
+ AbstractInt,
}
/** serializeScalarKind() serializes a ScalarKind to a BinaryStream */
@@ -1340,6 +2109,9 @@ function serializeScalarKind(s: BinaryStream, v: ScalarKind) {
case 'u8':
s.writeU8(SerializedScalarKind.U8);
return;
+ case 'abstract-int':
+ s.writeU8(SerializedScalarKind.AbstractInt);
+ return;
case 'i32':
s.writeU8(SerializedScalarKind.I32);
return;
@@ -1353,6 +2125,7 @@ function serializeScalarKind(s: BinaryStream, v: ScalarKind) {
s.writeU8(SerializedScalarKind.Bool);
return;
}
+ unreachable(`Do not know what to write scalar kind = ${v}`);
}
/** deserializeScalarKind() deserializes a ScalarKind from a BinaryStream */
@@ -1373,6 +2146,8 @@ function deserializeScalarKind(s: BinaryStream): ScalarKind {
return 'u16';
case SerializedScalarKind.U8:
return 'u8';
+ case SerializedScalarKind.AbstractInt:
+ return 'abstract-int';
case SerializedScalarKind.I32:
return 'i32';
case SerializedScalarKind.I16:
@@ -1394,51 +2169,66 @@ enum SerializedValueKind {
/** serializeValue() serializes a Value to a BinaryStream */
export function serializeValue(s: BinaryStream, v: Value) {
- const serializeScalar = (scalar: Scalar, kind: ScalarKind) => {
- switch (kind) {
- case 'abstract-float':
- s.writeF64(scalar.value as number);
- return;
- case 'f64':
- s.writeF64(scalar.value as number);
- return;
- case 'f32':
- s.writeF32(scalar.value as number);
- return;
- case 'f16':
- s.writeF16(scalar.value as number);
- return;
- case 'u32':
- s.writeU32(scalar.value as number);
- return;
- case 'u16':
- s.writeU16(scalar.value as number);
- return;
- case 'u8':
- s.writeU8(scalar.value as number);
- return;
- case 'i32':
- s.writeI32(scalar.value as number);
- return;
- case 'i16':
- s.writeI16(scalar.value as number);
- return;
- case 'i8':
- s.writeI8(scalar.value as number);
- return;
- case 'bool':
- s.writeBool(scalar.value as boolean);
- return;
+ const serializeScalar = (scalar: ScalarValue, kind: ScalarKind) => {
+ switch (typeof scalar.value) {
+ case 'number':
+ switch (kind) {
+ case 'abstract-float':
+ s.writeF64(scalar.value);
+ return;
+ case 'f64':
+ s.writeF64(scalar.value);
+ return;
+ case 'f32':
+ s.writeF32(scalar.value);
+ return;
+ case 'f16':
+ s.writeF16(scalar.value);
+ return;
+ case 'u32':
+ s.writeU32(scalar.value);
+ return;
+ case 'u16':
+ s.writeU16(scalar.value);
+ return;
+ case 'u8':
+ s.writeU8(scalar.value);
+ return;
+ case 'i32':
+ s.writeI32(scalar.value);
+ return;
+ case 'i16':
+ s.writeI16(scalar.value);
+ return;
+ case 'i8':
+ s.writeI8(scalar.value);
+ return;
+ }
+ break;
+ case 'bigint':
+ switch (kind) {
+ case 'abstract-int':
+ s.writeI64(scalar.value);
+ return;
+ }
+ break;
+ case 'boolean':
+ switch (kind) {
+ case 'bool':
+ s.writeBool(scalar.value);
+ return;
+ }
+ break;
}
};
- if (v instanceof Scalar) {
+ if (isScalarValue(v)) {
s.writeU8(SerializedValueKind.Scalar);
serializeScalarKind(s, v.type.kind);
serializeScalar(v, v.type.kind);
return;
}
- if (v instanceof Vector) {
+ if (v instanceof VectorValue) {
s.writeU8(SerializedValueKind.Vector);
serializeScalarKind(s, v.type.elementType.kind);
s.writeU8(v.type.width);
@@ -1447,7 +2237,7 @@ export function serializeValue(s: BinaryStream, v: Value) {
}
return;
}
- if (v instanceof Matrix) {
+ if (v instanceof MatrixValue) {
s.writeU8(SerializedValueKind.Matrix);
serializeScalarKind(s, v.type.elementType.kind);
s.writeU8(v.type.cols);
@@ -1481,6 +2271,8 @@ export function deserializeValue(s: BinaryStream): Value {
return u16(s.readU16());
case 'u8':
return u8(s.readU8());
+ case 'abstract-int':
+ return abstractInt(s.readI64());
case 'i32':
return i32(s.readI32());
case 'i16':
@@ -1498,23 +2290,23 @@ export function deserializeValue(s: BinaryStream): Value {
return deserializeScalar(scalarKind);
case SerializedValueKind.Vector: {
const width = s.readU8();
- const scalars = new Array<Scalar>(width);
+ const scalars = new Array<ScalarValue>(width);
for (let i = 0; i < width; i++) {
scalars[i] = deserializeScalar(scalarKind);
}
- return new Vector(scalars);
+ return new VectorValue(scalars);
}
case SerializedValueKind.Matrix: {
const numCols = s.readU8();
const numRows = s.readU8();
- const columns = new Array<Scalar[]>(numCols);
+ const columns = new Array<ScalarValue[]>(numCols);
for (let c = 0; c < numCols; c++) {
- columns[c] = new Array<Scalar>(numRows);
+ columns[c] = new Array<ScalarValue>(numRows);
for (let i = 0; i < numRows; i++) {
columns[c][i] = deserializeScalar(scalarKind);
}
}
- return new Matrix(columns);
+ return new MatrixValue(columns);
}
default:
unreachable(`invalid serialized value kind: ${valueKind}`);
@@ -1533,7 +2325,7 @@ export function isFloatValue(v: Value): boolean {
*/
export function isAbstractType(ty: Type): boolean {
if (ty instanceof ScalarType) {
- return ty.kind === 'abstract-float';
+ return ty.kind === 'abstract-float' || ty.kind === 'abstract-int';
}
return false;
}
@@ -1552,84 +2344,222 @@ export function isFloatType(ty: Type): boolean {
return false;
}
+/**
+ * @returns if `ty` is a type convertible to floating point type.
+ * @note this does not consider composite types.
+ * Use elementType() if you want to test the element type.
+ */
+export function isConvertibleToFloatType(ty: Type): boolean {
+ if (ty instanceof ScalarType) {
+ return (
+ ty.kind === 'abstract-int' ||
+ ty.kind === 'abstract-float' ||
+ ty.kind === 'f64' ||
+ ty.kind === 'f32' ||
+ ty.kind === 'f16'
+ );
+ }
+ return false;
+}
+
+/**
+ * @returns if `ty` is an unsigned type.
+ */
+export function isUnsignedType(ty: Type): boolean {
+ if (ty instanceof ScalarType) {
+ return ty.kind === 'u8' || ty.kind === 'u16' || ty.kind === 'u32';
+ } else {
+ return isUnsignedType(ty.elementType);
+ }
+}
+
+/** @returns true if an argument of type 'src' can be used for a parameter of type 'dst' */
+export function isConvertible(src: Type, dst: Type) {
+ if (src === dst) {
+ return true;
+ }
+
+ const widthOf = (ty: Type) => {
+ return ty instanceof VectorType ? ty.width : 1;
+ };
+
+ if (widthOf(src) !== widthOf(dst)) {
+ return false;
+ }
+
+ const elSrc = scalarTypeOf(src);
+ const elDst = scalarTypeOf(dst);
+
+ switch (elSrc.kind) {
+ case 'abstract-float':
+ switch (elDst.kind) {
+ case 'abstract-float':
+ case 'f16':
+ case 'f32':
+ case 'f64':
+ return true;
+ default:
+ return false;
+ }
+ case 'abstract-int':
+ switch (elDst.kind) {
+ case 'abstract-int':
+ case 'abstract-float':
+ case 'f16':
+ case 'f32':
+ case 'f64':
+ case 'u16':
+ case 'u32':
+ case 'u8':
+ case 'i16':
+ case 'i32':
+ case 'i8':
+ return true;
+ default:
+ return false;
+ }
+ default:
+ return false;
+ }
+}
+
/// All floating-point scalar types
-export const kAllFloatScalars = [TypeAbstractFloat, TypeF32, TypeF16] as const;
+const kFloatScalars = [Type.abstractFloat, Type.f32, Type.f16] as const;
/// All floating-point vec2 types
-export const kAllFloatVector2 = [
- TypeVec(2, TypeAbstractFloat),
- TypeVec(2, TypeF32),
- TypeVec(2, TypeF16),
-] as const;
+const kFloatVec2 = [Type.vec2af, Type.vec2f, Type.vec2h] as const;
/// All floating-point vec3 types
-export const kAllFloatVector3 = [
- TypeVec(3, TypeAbstractFloat),
- TypeVec(3, TypeF32),
- TypeVec(3, TypeF16),
-] as const;
+const kFloatVec3 = [Type.vec3af, Type.vec3f, Type.vec3h] as const;
/// All floating-point vec4 types
-export const kAllFloatVector4 = [
- TypeVec(4, TypeAbstractFloat),
- TypeVec(4, TypeF32),
- TypeVec(4, TypeF16),
+const kFloatVec4 = [Type.vec4af, Type.vec4f, Type.vec4h] as const;
+
+export const kConcreteF32ScalarsAndVectors = [
+ Type.f32,
+ Type.vec2f,
+ Type.vec3f,
+ Type.vec4f,
] as const;
-/// All floating-point vector types
-export const kAllFloatVectors = [
- ...kAllFloatVector2,
- ...kAllFloatVector3,
- ...kAllFloatVector4,
+/// All f16 floating-point scalar and vector types
+export const kConcreteF16ScalarsAndVectors = [
+ Type.f16,
+ Type.vec2h,
+ Type.vec3h,
+ Type.vec4h,
] as const;
+/// All floating-point vector types
+export const kFloatVectors = [...kFloatVec2, ...kFloatVec3, ...kFloatVec4] as const;
+
/// All floating-point scalar and vector types
-export const kAllFloatScalarsAndVectors = [...kAllFloatScalars, ...kAllFloatVectors] as const;
-
-/// All integer scalar and vector types
-export const kAllIntegerScalarsAndVectors = [
- TypeI32,
- TypeVec(2, TypeI32),
- TypeVec(3, TypeI32),
- TypeVec(4, TypeI32),
- TypeU32,
- TypeVec(2, TypeU32),
- TypeVec(3, TypeU32),
- TypeVec(4, TypeU32),
+export const kFloatScalarsAndVectors = [...kFloatScalars, ...kFloatVectors] as const;
+
+// Abstract and concrete integer types are not grouped into an 'all' type,
+// because for many validation tests there is a valid conversion of
+// AbstractInt -> AbstractFloat, but not one for the concrete integers. Thus, an
+// AbstractInt literal will be a potentially valid input, whereas the concrete
+// integers will not be. For many tests the pattern is to have separate fixtures
+// for the things that might be valid and those that are never valid.
+
+/// All signed integer vector types
+export const kConcreteSignedIntegerVectors = [Type.vec2i, Type.vec3i, Type.vec4i] as const;
+
+/// All unsigned integer vector types
+export const kConcreteUnsignedIntegerVectors = [Type.vec2u, Type.vec3u, Type.vec4u] as const;
+
+/// All concrete integer vector types
+export const kConcreteIntegerVectors = [
+ ...kConcreteSignedIntegerVectors,
+ ...kConcreteUnsignedIntegerVectors,
] as const;
/// All signed integer scalar and vector types
-export const kAllSignedIntegerScalarsAndVectors = [
- TypeI32,
- TypeVec(2, TypeI32),
- TypeVec(3, TypeI32),
- TypeVec(4, TypeI32),
+export const kConcreteSignedIntegerScalarsAndVectors = [
+ Type.i32,
+ ...kConcreteSignedIntegerVectors,
] as const;
/// All unsigned integer scalar and vector types
-export const kAllUnsignedIntegerScalarsAndVectors = [
- TypeU32,
- TypeVec(2, TypeU32),
- TypeVec(3, TypeU32),
- TypeVec(4, TypeU32),
+export const kConcreteUnsignedIntegerScalarsAndVectors = [
+ Type.u32,
+ ...kConcreteUnsignedIntegerVectors,
] as const;
-/// All floating-point and integer scalar and vector types
-export const kAllFloatAndIntegerScalarsAndVectors = [
- ...kAllFloatScalarsAndVectors,
- ...kAllIntegerScalarsAndVectors,
+/// All concrete integer scalar and vector types
+export const kConcreteIntegerScalarsAndVectors = [
+ ...kConcreteSignedIntegerScalarsAndVectors,
+ ...kConcreteUnsignedIntegerScalarsAndVectors,
] as const;
-/// All floating-point and signed integer scalar and vector types
-export const kAllFloatAndSignedIntegerScalarsAndVectors = [
- ...kAllFloatScalarsAndVectors,
- ...kAllSignedIntegerScalarsAndVectors,
+/// All types which are convertable to floating-point scalar types.
+export const kConvertableToFloatScalar = [Type.abstractInt, ...kFloatScalars] as const;
+
+/// All types which are convertable to floating-point vector 2 types.
+export const kConvertableToFloatVec2 = [Type.vec2ai, ...kFloatVec2] as const;
+
+/// All types which are convertable to floating-point vector 3 types.
+export const kConvertableToFloatVec3 = [Type.vec3ai, ...kFloatVec3] as const;
+
+/// All types which are convertable to floating-point vector 4 types.
+export const kConvertableToFloatVec4 = [Type.vec4ai, ...kFloatVec4] as const;
+
+/// All the types which are convertable to floating-point vector types.
+export const kConvertableToFloatVectors = [
+ Type.vec2ai,
+ Type.vec3ai,
+ Type.vec4ai,
+ ...kFloatVectors,
] as const;
-/** @returns the inner element type of the given type */
-export function elementType(t: ScalarType | VectorType | MatrixType) {
- if (t instanceof ScalarType) {
- return t;
- }
- return t.elementType;
-}
+/// All types which are convertable to floating-point scalar or vector types.
+export const kConvertableToFloatScalarsAndVectors = [
+ Type.abstractInt,
+ ...kFloatScalars,
+ ...kConvertableToFloatVectors,
+] as const;
+
+/// All the numeric scalar and vector types.
+export const kAllNumericScalarsAndVectors = [
+ ...kConvertableToFloatScalarsAndVectors,
+ ...kConcreteIntegerScalarsAndVectors,
+] as const;
+
+/// All the concrete integer and floating point scalars and vectors.
+export const kConcreteNumericScalarsAndVectors = [
+ ...kConcreteIntegerScalarsAndVectors,
+ ...kConcreteF16ScalarsAndVectors,
+ ...kConcreteF32ScalarsAndVectors,
+] as const;
+
+/// All boolean types.
+export const kAllBoolScalarsAndVectors = [Type.bool, Type.vec2b, Type.vec3b, Type.vec4b] as const;
+
+/// All the scalar and vector types.
+export const kAllScalarsAndVectors = [
+ ...kAllBoolScalarsAndVectors,
+ ...kAllNumericScalarsAndVectors,
+] as const;
+
+/// All the matrix types
+export const kAllMatrices = [
+ Type.mat2x2f,
+ Type.mat2x2h,
+ Type.mat2x3f,
+ Type.mat2x3h,
+ Type.mat2x4f,
+ Type.mat2x4h,
+ Type.mat3x2f,
+ Type.mat3x2h,
+ Type.mat3x3f,
+ Type.mat3x3h,
+ Type.mat3x4f,
+ Type.mat3x4h,
+ Type.mat4x2f,
+ Type.mat4x2h,
+ Type.mat4x3f,
+ Type.mat4x3h,
+ Type.mat4x4f,
+ Type.mat4x4h,
+] as const;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/device_pool.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/device_pool.ts
index 1e6c0402cb..8a90f43248 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/device_pool.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/device_pool.ts
@@ -9,7 +9,12 @@ import {
} from '../../common/util/util.js';
import { getDefaultLimits, kLimits } from '../capability_info.js';
+// MUST_NOT_BE_IMPORTED_BY_DATA_CACHE
+// This file should not be transitively imported by .cache.ts files
+
export interface DeviceProvider {
+ /** Adapter the device was created from. Cannot be reused; just for adapter info. */
+ readonly adapter: GPUAdapter;
readonly device: GPUDevice;
expectDeviceLost(reason: GPUDeviceLostReason): void;
}
@@ -283,6 +288,8 @@ type DeviceHolderState = 'free' | 'acquired';
* Holds a GPUDevice and tracks its state (free/acquired) and handles device loss.
*/
class DeviceHolder implements DeviceProvider {
+ /** Adapter the device was created from. Cannot be reused; just for adapter info. */
+ readonly adapter: GPUAdapter;
/** The device. Will be cleared during cleanup if there were unexpected errors. */
private _device: GPUDevice | undefined;
/** Whether the device is in use by a test or not. */
@@ -307,10 +314,11 @@ class DeviceHolder implements DeviceProvider {
const device = await adapter.requestDevice(descriptor);
assert(device !== null, 'requestDevice returned null');
- return new DeviceHolder(device);
+ return new DeviceHolder(adapter, device);
}
- private constructor(device: GPUDevice) {
+ private constructor(adapter: GPUAdapter, device: GPUDevice) {
+ this.adapter = adapter;
this._device = device;
void this._device.lost.then(ev => {
this.lostInfo = ev;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/floating_point.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/floating_point.ts
index e271e7db7a..b644052ebf 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/floating_point.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/floating_point.ts
@@ -1,7 +1,8 @@
import { ROArrayArray, ROArrayArrayArray } from '../../common/util/types.js';
import { assert, unreachable } from '../../common/util/util.js';
import { Float16Array } from '../../external/petamoriken/float16/float16.js';
-import { Case, IntervalFilter } from '../shader/execution/expression/expression.js';
+import { Case } from '../shader/execution/expression/case.js';
+import { IntervalFilter } from '../shader/execution/expression/interval_filter.js';
import BinaryStream from './binary_stream.js';
import { anyOf } from './compare.js';
@@ -11,7 +12,7 @@ import {
f16,
f32,
isFloatType,
- Scalar,
+ ScalarValue,
ScalarType,
toMatrix,
toVector,
@@ -23,6 +24,7 @@ import {
correctlyRoundedF16,
correctlyRoundedF32,
correctlyRoundedF64,
+ every2DArray,
flatten2DArray,
FlushMode,
flushSubnormalNumberF16,
@@ -36,10 +38,24 @@ import {
map2DArray,
oneULPF16,
oneULPF32,
- quantizeToF32,
quantizeToF16,
+ quantizeToF32,
+ scalarF16Range,
+ scalarF32Range,
+ scalarF64Range,
+ sparseMatrixF16Range,
+ sparseMatrixF32Range,
+ sparseMatrixF64Range,
+ sparseScalarF16Range,
+ sparseScalarF32Range,
+ sparseScalarF64Range,
+ sparseVectorF16Range,
+ sparseVectorF32Range,
+ sparseVectorF64Range,
unflatten2DArray,
- every2DArray,
+ vectorF16Range,
+ vectorF32Range,
+ vectorF64Range,
} from './math.js';
/** Indicate the kind of WGSL floating point numbers being operated on */
@@ -83,12 +99,12 @@ export function deserializeFPKind(s: BinaryStream): FPKind {
// Containers
/**
- * Representation of bounds for an interval as an array with either one or two
- * elements. Single element indicates that the interval is a single point. For
- * two elements, the first is the lower bound of the interval and the second is
- * the upper bound.
+ * Representation of endpoints for an interval as an array with either one or
+ * two elements. Single element indicates that the interval is a single point.
+ * For two elements, the first is the lower edges of the interval and the
+ * second is the upper edge, i.e. e[0] <= e[1], where e is an IntervalEndpoints
*/
-export type IntervalBounds = readonly [number] | readonly [number, number];
+export type IntervalEndpoints = readonly [number] | readonly [number, number];
/** Represents a closed interval of floating point numbers */
export class FPInterval {
@@ -102,15 +118,18 @@ export class FPInterval {
* `FPTraits.toInterval` is the preferred way to create FPIntervals
*
* @param kind the floating point number type this is an interval for
- * @param bounds beginning and end of the interval
+ * @param endpoints beginning and end of the interval
*/
- public constructor(kind: FPKind, ...bounds: IntervalBounds) {
+ public constructor(kind: FPKind, ...endpoints: IntervalEndpoints) {
this.kind = kind;
- const begin = bounds[0];
- const end = bounds.length === 2 ? bounds[1] : bounds[0];
- assert(!Number.isNaN(begin) && !Number.isNaN(end), `bounds need to be non-NaN`);
- assert(begin <= end, `bounds[0] (${begin}) must be less than or equal to bounds[1] (${end})`);
+ const begin = endpoints[0];
+ const end = endpoints.length === 2 ? endpoints[1] : endpoints[0];
+ assert(!Number.isNaN(begin) && !Number.isNaN(end), `endpoints need to be non-NaN`);
+ assert(
+ begin <= end,
+ `endpoints[0] (${begin}) must be less than or equal to endpoints[1] (${end})`
+ );
this.begin = begin;
this.end = end;
@@ -122,7 +141,7 @@ export class FPInterval {
}
/** @returns begin and end if non-point interval, otherwise just begin */
- public bounds(): IntervalBounds {
+ public endpoints(): IntervalEndpoints {
return this.isPoint() ? [this.begin] : [this.begin, this.end];
}
@@ -163,7 +182,7 @@ export class FPInterval {
/** @returns a string representation for logging purposes */
public toString(): string {
- return `{ '${this.kind}', [${this.bounds().map(this.traits().scalarBuilder)}] }`;
+ return `{ '${this.kind}', [${this.endpoints().map(this.traits().scalarBuilder)}] }`;
}
}
@@ -312,7 +331,7 @@ interface ScalarToIntervalOp {
* occur and returns a span of those points to be used as the domain instead.
*
* Used by this.runScalarToIntervalOp before invoking impl.
- * If not defined, the bounds of the existing domain are assumed to be the
+ * If not defined, the endpoints of the existing domain are assumed to be the
* extrema.
*
* This is only implemented for operations that meet all the following
@@ -323,6 +342,14 @@ interface ScalarToIntervalOp {
* i.e. fooInterval takes in x: number | FPInterval, not x: number
*/
extrema?: (x: FPInterval) => FPInterval;
+
+ /**
+ * Restricts the inputs to operation to the given domain.
+ *
+ * Only defined for operations that have tighter domain requirements than 'must
+ * be finite'.
+ */
+ domain?: () => FPInterval;
}
/**
@@ -334,6 +361,13 @@ export interface ScalarPairToInterval {
(x: number, y: number): FPInterval;
}
+/** Domain for a ScalarPairToInterval implementation */
+interface ScalarPairToIntervalDomain {
+ // Arrays to support discrete valid domain intervals
+ x: readonly FPInterval[];
+ y: readonly FPInterval[];
+}
+
/** Operation used to implement a ScalarPairToInterval */
interface ScalarPairToIntervalOp {
/** @returns acceptance interval for a function at point (x, y) */
@@ -343,23 +377,24 @@ interface ScalarPairToIntervalOp {
* occur and returns spans of those points to be used as the domain instead.
*
* Used by runScalarPairToIntervalOp before invoking impl.
- * If not defined, the bounds of the existing domain are assumed to be the
+ * If not defined, the endpoints of the existing domain are assumed to be the
* extrema.
*
- * This is only implemented for functions that meet all of the following
+ * This is only implemented for functions that meet all the following
* criteria:
* a) non-monotonic
* b) used in inherited accuracy calculations
* c) need to take in an interval for b)
*/
extrema?: (x: FPInterval, y: FPInterval) => [FPInterval, FPInterval];
-}
-/** Domain for a ScalarPairToInterval implementation */
-interface ScalarPairToIntervalDomain {
- // Arrays to support discrete valid domain intervals
- x: readonly FPInterval[];
- y: readonly FPInterval[];
+ /**
+ * Restricts the inputs to operation to the given domain.
+ *
+ * Only defined for operations that have tighter domain requirements than 'must
+ * be finite'.
+ */
+ domain?: () => ScalarPairToIntervalDomain;
}
/**
@@ -556,7 +591,7 @@ export interface VectorMatrixToVector {
// Traits
/**
- * Typed structure containing all the limits/constants defined for each
+ * Typed structure containing all the constants defined for each
* WGSL floating point kind
*/
interface FPConstants {
@@ -599,10 +634,12 @@ interface FPConstants {
sixth: number;
};
};
+ bias: number;
unboundedInterval: FPInterval;
zeroInterval: FPInterval;
negPiToPiInterval: FPInterval;
greaterThanZeroInterval: FPInterval;
+ negOneToOneInterval: FPInterval;
zeroVector: {
2: FPVector;
3: FPVector;
@@ -635,7 +672,7 @@ interface FPConstants {
/** A representation of an FPInterval for a case param */
export type FPIntervalParam = {
kind: FPKind;
- interval: number | IntervalBounds;
+ interval: number | IntervalEndpoints;
};
/** Abstract base class for all floating-point traits */
@@ -650,7 +687,7 @@ export abstract class FPTraits {
// Utilities - Implemented
/** @returns an interval containing the point or the original interval */
- public toInterval(n: number | IntervalBounds | FPInterval): FPInterval {
+ public toInterval(n: number | IntervalEndpoints | FPInterval): FPInterval {
if (n instanceof FPInterval) {
if (n.kind === this.kind) {
return n;
@@ -661,7 +698,7 @@ export abstract class FPTraits {
return this.constants().unboundedInterval;
}
- return new FPInterval(this.kind, ...n.bounds());
+ return new FPInterval(this.kind, ...n.endpoints());
}
if (n instanceof Array) {
@@ -674,7 +711,7 @@ export abstract class FPTraits {
/**
* Makes a param that can be turned into an interval
*/
- public toParam(n: number | IntervalBounds): FPIntervalParam {
+ public toParam(n: number | IntervalEndpoints): FPIntervalParam {
return {
kind: this.kind,
interval: n,
@@ -685,18 +722,18 @@ export abstract class FPTraits {
* Converts p into an FPInterval if it is an FPIntervalPAram
*/
public fromParam(
- p: number | IntervalBounds | FPIntervalParam
- ): number | IntervalBounds | FPInterval {
+ p: number | IntervalEndpoints | FPIntervalParam
+ ): number | IntervalEndpoints | FPInterval {
const param = p as FPIntervalParam;
if (param.interval && param.kind) {
assert(param.kind === this.kind);
return this.toInterval(param.interval);
}
- return p as number | IntervalBounds;
+ return p as number | IntervalEndpoints;
}
/**
- * @returns an interval with the tightest bounds that includes all provided
+ * @returns an interval with the tightest endpoints that includes all provided
* intervals
*/
public spanIntervals(...intervals: readonly FPInterval[]): FPInterval {
@@ -715,7 +752,7 @@ export abstract class FPTraits {
}
/** Narrow an array of values to FPVector if possible */
- public isVector(v: ReadonlyArray<number | IntervalBounds | FPInterval>): v is FPVector {
+ public isVector(v: ReadonlyArray<number | IntervalEndpoints | FPInterval>): v is FPVector {
if (v.every(e => e instanceof FPInterval && e.kind === this.kind)) {
return v.length === 2 || v.length === 3 || v.length === 4;
}
@@ -723,7 +760,7 @@ export abstract class FPTraits {
}
/** @returns an FPVector representation of an array of values if possible */
- public toVector(v: ReadonlyArray<number | IntervalBounds | FPInterval>): FPVector {
+ public toVector(v: ReadonlyArray<number | IntervalEndpoints | FPInterval>): FPVector {
if (this.isVector(v) && v.every(e => e.kind === this.kind)) {
return v;
}
@@ -762,7 +799,7 @@ export abstract class FPTraits {
}
/** Narrow an array of an array of values to FPMatrix if possible */
- public isMatrix(m: Array2D<number | IntervalBounds | FPInterval> | FPVector[]): m is FPMatrix {
+ public isMatrix(m: Array2D<number | IntervalEndpoints | FPInterval> | FPVector[]): m is FPMatrix {
if (!m.every(c => c.every(e => e instanceof FPInterval && e.kind === this.kind))) {
return false;
}
@@ -787,7 +824,7 @@ export abstract class FPTraits {
}
/** @returns an FPMatrix representation of an array of an array of values if possible */
- public toMatrix(m: Array2D<number | IntervalBounds | FPInterval> | FPVector[]): FPMatrix {
+ public toMatrix(m: Array2D<number | IntervalEndpoints | FPInterval> | FPVector[]): FPMatrix {
if (
this.isMatrix(m) &&
every2DArray(m, (e: FPInterval) => {
@@ -840,48 +877,6 @@ export abstract class FPTraits {
return needs_zero ? values.concat(0) : values;
}
- /**
- * Restrict the inputs to an ScalarToInterval operation
- *
- * Only used for operations that have tighter domain requirements than 'must
- * be finite'.
- *
- * @param domain interval to restrict inputs to
- * @param impl operation implementation to run if input is within the required domain
- * @returns a ScalarToInterval that calls impl if domain contains the input,
- * otherwise it returns an unbounded interval */
- protected limitScalarToIntervalDomain(
- domain: FPInterval,
- impl: ScalarToInterval
- ): ScalarToInterval {
- return (n: number): FPInterval => {
- return domain.contains(n) ? impl(n) : this.constants().unboundedInterval;
- };
- }
-
- /**
- * Restrict the inputs to a ScalarPairToInterval
- *
- * Only used for operations that have tighter domain requirements than 'must be
- * finite'.
- *
- * @param domain set of intervals to restrict inputs to
- * @param impl operation implementation to run if input is within the required domain
- * @returns a ScalarPairToInterval that calls impl if domain contains the input,
- * otherwise it returns an unbounded interval */
- protected limitScalarPairToIntervalDomain(
- domain: ScalarPairToIntervalDomain,
- impl: ScalarPairToInterval
- ): ScalarPairToInterval {
- return (x: number, y: number): FPInterval => {
- if (!domain.x.some(d => d.contains(x)) || !domain.y.some(d => d.contains(y))) {
- return this.constants().unboundedInterval;
- }
-
- return impl(x, y);
- };
- }
-
/** Stub for scalar to interval generator */
protected unimplementedScalarToInterval(name: string, _x: number | FPInterval): FPInterval {
unreachable(`'${name}' is not yet implemented for '${this.kind}'`);
@@ -1053,14 +1048,14 @@ export abstract class FPTraits {
unreachable(`'refract' is not yet implemented for '${this.kind}'`);
}
- /** Version of absoluteErrorInterval that always returns the unboundedInterval */
- protected unboundedAbsoluteErrorInterval(_n: number, _error_range: number): FPInterval {
- return this.constants().unboundedInterval;
+ /** Stub for absolute errors */
+ protected unimplementedAbsoluteErrorInterval(_n: number, _error_range: number): FPInterval {
+ unreachable(`Absolute Error is not implement for '${this.kind}'`);
}
- /** Version of ulpInterval that always returns the unboundedInterval */
- protected unboundedUlpInterval(_n: number, _numULP: number): FPInterval {
- return this.constants().unboundedInterval;
+ /** Stub for ULP errors */
+ protected unimplementedUlpInterval(_n: number, _numULP: number): FPInterval {
+ unreachable(`ULP Error is not implement for '${this.kind}'`);
}
// Utilities - Defined by subclass
@@ -1079,8 +1074,22 @@ export abstract class FPTraits {
public abstract readonly flushSubnormal: (n: number) => number;
/** @returns 1 * ULP: (number) */
public abstract readonly oneULP: (target: number, mode?: FlushMode) => number;
- /** @returns a builder for converting numbers to Scalars */
- public abstract readonly scalarBuilder: (n: number) => Scalar;
+ /** @returns a builder for converting numbers to ScalarsValues */
+ public abstract readonly scalarBuilder: (n: number) => ScalarValue;
+ /** @returns a range of scalars for testing */
+ public abstract scalarRange(): readonly number[];
+ /** @returns a reduced range of scalars for testing */
+ public abstract sparseScalarRange(): readonly number[];
+ /** @returns a range of dim element vectors for testing */
+ public abstract vectorRange(dim: number): ROArrayArray<number>;
+ /** @returns a reduced range of dim element vectors for testing */
+ public abstract sparseVectorRange(dim: number): ROArrayArray<number>;
+ /** @returns a reduced range of cols x rows matrices for testing
+ *
+ * A non-sparse version of this generator is intentionally not provided due to
+ * runtime issues with more dense ranges.
+ */
+ public abstract sparseMatrixRange(cols: number, rows: number): ROArrayArrayArray<number>;
// Framework - Cases
@@ -1705,7 +1714,6 @@ export abstract class FPTraits {
): Case | undefined {
param0 = map2DArray(param0, this.quantize);
param1 = map2DArray(param1, this.quantize);
-
const results = ops.map(o => o(param0, param1));
if (filter === 'finite' && results.some(m => m.some(c => c.some(r => !r.isFinite())))) {
return undefined;
@@ -1973,6 +1981,15 @@ export abstract class FPTraits {
assert(!Number.isNaN(n), `flush not defined for NaN`);
const values = this.correctlyRounded(n);
const inputs = this.addFlushedIfNeeded(values);
+
+ if (op.domain !== undefined) {
+ // Cannot invoke op.domain() directly in the .some, because the narrowing doesn't propegate.
+ const domain = op.domain();
+ if (inputs.some(i => !domain.contains(i))) {
+ return this.constants().unboundedInterval;
+ }
+ }
+
const results = new Set<FPInterval>(inputs.map(op.impl));
return this.spanIntervals(...results);
}
@@ -1998,10 +2015,25 @@ export abstract class FPTraits {
): FPInterval {
assert(!Number.isNaN(x), `flush not defined for NaN`);
assert(!Number.isNaN(y), `flush not defined for NaN`);
+
const x_values = this.correctlyRounded(x);
const y_values = this.correctlyRounded(y);
const x_inputs = this.addFlushedIfNeeded(x_values);
const y_inputs = this.addFlushedIfNeeded(y_values);
+
+ if (op.domain !== undefined) {
+ // Cannot invoke op.domain() directly in the .some, because the narrowing doesn't propegate.
+ const domain = op.domain();
+
+ if (x_inputs.some(i => !domain.x.some(e => e.contains(i)))) {
+ return this.constants().unboundedInterval;
+ }
+
+ if (y_inputs.some(j => !domain.y.some(e => e.contains(j)))) {
+ return this.constants().unboundedInterval;
+ }
+ }
+
const intervals = new Set<FPInterval>();
x_inputs.forEach(inner_x => {
y_inputs.forEach(inner_y => {
@@ -2252,7 +2284,7 @@ export abstract class FPTraits {
}
const result = this.spanIntervals(
- ...x.bounds().map(b => this.roundAndFlushScalarToInterval(b, op))
+ ...x.endpoints().map(b => this.roundAndFlushScalarToInterval(b, op))
);
return result.isFinite() ? result : this.constants().unboundedInterval;
}
@@ -2282,8 +2314,8 @@ export abstract class FPTraits {
}
const outputs = new Set<FPInterval>();
- x.bounds().forEach(inner_x => {
- y.bounds().forEach(inner_y => {
+ x.endpoints().forEach(inner_x => {
+ y.endpoints().forEach(inner_y => {
outputs.add(this.roundAndFlushScalarPairToInterval(inner_x, inner_y, op));
});
});
@@ -2312,9 +2344,9 @@ export abstract class FPTraits {
}
const outputs = new Set<FPInterval>();
- x.bounds().forEach(inner_x => {
- y.bounds().forEach(inner_y => {
- z.bounds().forEach(inner_z => {
+ x.endpoints().forEach(inner_x => {
+ y.endpoints().forEach(inner_y => {
+ z.endpoints().forEach(inner_z => {
outputs.add(this.roundAndFlushScalarTripleToInterval(inner_x, inner_y, inner_z, op));
});
});
@@ -2337,7 +2369,7 @@ export abstract class FPTraits {
return this.constants().unboundedInterval;
}
- const x_values = cartesianProduct<number>(...x.map(e => e.bounds()));
+ const x_values = cartesianProduct<number>(...x.map(e => e.endpoints()));
const outputs = new Set<FPInterval>();
x_values.forEach(inner_x => {
@@ -2366,8 +2398,8 @@ export abstract class FPTraits {
return this.constants().unboundedInterval;
}
- const x_values = cartesianProduct<number>(...x.map(e => e.bounds()));
- const y_values = cartesianProduct<number>(...y.map(e => e.bounds()));
+ const x_values = cartesianProduct<number>(...x.map(e => e.endpoints()));
+ const y_values = cartesianProduct<number>(...y.map(e => e.endpoints()));
const outputs = new Set<FPInterval>();
x_values.forEach(inner_x => {
@@ -2393,7 +2425,7 @@ export abstract class FPTraits {
return this.constants().unboundedVector[x.length];
}
- const x_values = cartesianProduct<number>(...x.map(e => e.bounds()));
+ const x_values = cartesianProduct<number>(...x.map(e => e.endpoints()));
const outputs = new Set<FPVector>();
x_values.forEach(inner_x => {
@@ -2437,8 +2469,8 @@ export abstract class FPTraits {
return this.constants().unboundedVector[x.length];
}
- const x_values = cartesianProduct<number>(...x.map(e => e.bounds()));
- const y_values = cartesianProduct<number>(...y.map(e => e.bounds()));
+ const x_values = cartesianProduct<number>(...x.map(e => e.endpoints()));
+ const y_values = cartesianProduct<number>(...y.map(e => e.endpoints()));
const outputs = new Set<FPVector>();
x_values.forEach(inner_x => {
@@ -2495,12 +2527,15 @@ export abstract class FPTraits {
protected runMatrixToMatrixOp(m: FPMatrix, op: MatrixToMatrixOp): FPMatrix {
const num_cols = m.length;
const num_rows = m[0].length;
- if (m.some(c => c.some(r => !r.isFinite()))) {
- return this.constants().unboundedMatrix[num_cols][num_rows];
- }
+
+ // Do not check for OOB inputs and exit early here, because the shape of
+ // the output matrix may be determined by the operation being run,
+ // i.e. transpose.
const m_flat: readonly FPInterval[] = flatten2DArray(m);
- const m_values: ROArrayArray<number> = cartesianProduct<number>(...m_flat.map(e => e.bounds()));
+ const m_values: ROArrayArray<number> = cartesianProduct<number>(
+ ...m_flat.map(e => e.endpoints())
+ );
const outputs = new Set<FPMatrix>();
m_values.forEach(inner_m => {
@@ -2522,6 +2557,33 @@ export abstract class FPTraits {
/**
* Calculate the Matrix of acceptance intervals by running a scalar operation
+ * component-wise over a scalar and a matrix.
+ *
+ * An example of this is performing constant scaling.
+ *
+ * @param i scalar input
+ * @param m matrix input
+ * @param op scalar operation to be run component-wise
+ * @returns a matrix of intervals with the outputs of op.impl
+ */
+ protected runScalarPairToIntervalOpScalarMatrixComponentWise(
+ i: FPInterval,
+ m: FPMatrix,
+ op: ScalarPairToIntervalOp
+ ): FPMatrix {
+ const cols = m.length;
+ const rows = m[0].length;
+ return this.toMatrix(
+ unflatten2DArray(
+ flatten2DArray(m).map(e => this.runScalarPairToIntervalOp(i, e, op)),
+ cols,
+ rows
+ )
+ );
+ }
+
+ /**
+ * Calculate the Matrix of acceptance intervals by running a scalar operation
* component-wise over a pair of matrices.
*
* An example of this is performing matrix addition.
@@ -2531,14 +2593,14 @@ export abstract class FPTraits {
* @param op scalar operation to be run component-wise
* @returns a matrix of intervals with the outputs of op.impl
*/
- protected runScalarPairToIntervalOpMatrixComponentWise(
+ protected runScalarPairToIntervalOpMatrixMatrixComponentWise(
x: FPMatrix,
y: FPMatrix,
op: ScalarPairToIntervalOp
): FPMatrix {
assert(
x.length === y.length && x[0].length === y[0].length,
- `runScalarPairToIntervalOpMatrixComponentWise requires matrices of the same dimensions`
+ `runScalarPairToIntervalOpMatrixMatrixComponentWise requires matrices of the same dimensions`
);
const cols = x.length;
@@ -2673,7 +2735,7 @@ export abstract class FPTraits {
// This op is implemented differently for f32 and f16.
private readonly AcosIntervalOp: ScalarToIntervalOp = {
- impl: this.limitScalarToIntervalDomain(this.toInterval([-1.0, 1.0]), (n: number) => {
+ impl: (n: number) => {
assert(this.kind === 'f32' || this.kind === 'f16');
// acos(n) = atan2(sqrt(1.0 - n * n), n) or a polynomial approximation with absolute error
const y = this.sqrtInterval(this.subtractionInterval(1, this.multiplicationInterval(n, n)));
@@ -2682,7 +2744,10 @@ export abstract class FPTraits {
this.atan2Interval(y, n),
this.absoluteErrorInterval(Math.acos(n), approx_abs_error)
);
- }),
+ },
+ domain: () => {
+ return this.constants().negOneToOneInterval;
+ },
};
protected acosIntervalImpl(n: number): FPInterval {
@@ -2751,7 +2816,7 @@ export abstract class FPTraits {
) => FPInterval;
protected additionMatrixMatrixIntervalImpl(x: Array2D<number>, y: Array2D<number>): FPMatrix {
- return this.runScalarPairToIntervalOpMatrixComponentWise(
+ return this.runScalarPairToIntervalOpMatrixMatrixComponentWise(
this.toMatrix(x),
this.toMatrix(y),
this.AdditionIntervalOp
@@ -2766,16 +2831,19 @@ export abstract class FPTraits {
// This op is implemented differently for f32 and f16.
private readonly AsinIntervalOp: ScalarToIntervalOp = {
- impl: this.limitScalarToIntervalDomain(this.toInterval([-1.0, 1.0]), (n: number) => {
+ impl: (n: number) => {
assert(this.kind === 'f32' || this.kind === 'f16');
// asin(n) = atan2(n, sqrt(1.0 - n * n)) or a polynomial approximation with absolute error
const x = this.sqrtInterval(this.subtractionInterval(1, this.multiplicationInterval(n, n)));
- const approx_abs_error = this.kind === 'f32' ? 6.77e-5 : 3.91e-3;
+ const approx_abs_error = this.kind === 'f32' ? 6.81e-5 : 3.91e-3;
return this.spanIntervals(
this.atan2Interval(n, x),
this.absoluteErrorInterval(Math.asin(n), approx_abs_error)
);
- }),
+ },
+ domain: () => {
+ return this.constants().negOneToOneInterval;
+ },
};
/** Calculate an acceptance interval for asin(n) */
@@ -2836,29 +2904,23 @@ export abstract class FPTraits {
: [this.toInterval([-(2 ** 14), -(2 ** -14)]), this.toInterval([2 ** -14, 2 ** 14])];
const ulp_error = this.kind === 'f32' ? 4096 : 5;
return {
- impl: this.limitScalarPairToIntervalDomain(
- {
- x: domain_x,
- y: domain_y,
- },
- (y: number, x: number): FPInterval => {
- // Accurate result in f64
- let atan_yx = Math.atan(y / x);
- // Offset by +/-pi according to the definition. Use pi value in f64 because we are
- // handling accurate result.
- if (x < 0) {
- // x < 0, y > 0, result is atan(y/x) + π
- if (y > 0) {
- atan_yx = atan_yx + kValue.f64.positive.pi.whole;
- } else {
- // x < 0, y < 0, result is atan(y/x) - π
- atan_yx = atan_yx - kValue.f64.positive.pi.whole;
- }
+ impl: (y: number, x: number): FPInterval => {
+ // Accurate result in f64
+ let atan_yx = Math.atan(y / x);
+ // Offset by +/-pi according to the definition. Use pi value in f64 because we are
+ // handling accurate result.
+ if (x < 0) {
+ // x < 0, y > 0, result is atan(y/x) + π
+ if (y > 0) {
+ atan_yx = atan_yx + kValue.f64.positive.pi.whole;
+ } else {
+ // x < 0, y < 0, result is atan(y/x) - π
+ atan_yx = atan_yx - kValue.f64.positive.pi.whole;
}
-
- return this.ulpInterval(atan_yx, ulp_error);
}
- ),
+
+ return this.ulpInterval(atan_yx, ulp_error);
+ },
extrema: (y: FPInterval, x: FPInterval): [FPInterval, FPInterval] => {
// There is discontinuity, which generates an unbounded result, at y/x = 0 that will dominate the accuracy
if (y.contains(0)) {
@@ -2869,6 +2931,9 @@ export abstract class FPTraits {
}
return [y, x];
},
+ domain: () => {
+ return { x: domain_x, y: domain_y };
+ },
};
}
@@ -2984,14 +3049,14 @@ export abstract class FPTraits {
public abstract readonly clampIntervals: ScalarTripleToInterval[];
private readonly CosIntervalOp: ScalarToIntervalOp = {
- impl: this.limitScalarToIntervalDomain(
- this.constants().negPiToPiInterval,
- (n: number): FPInterval => {
- assert(this.kind === 'f32' || this.kind === 'f16');
- const abs_error = this.kind === 'f32' ? 2 ** -11 : 2 ** -7;
- return this.absoluteErrorInterval(Math.cos(n), abs_error);
- }
- ),
+ impl: (n: number): FPInterval => {
+ assert(this.kind === 'f32' || this.kind === 'f16');
+ const abs_error = this.kind === 'f32' ? 2 ** -11 : 2 ** -7;
+ return this.absoluteErrorInterval(Math.cos(n), abs_error);
+ },
+ domain: () => {
+ return this.constants().negPiToPiInterval;
+ },
};
protected cosIntervalImpl(n: number): FPInterval {
@@ -3041,7 +3106,11 @@ export abstract class FPTraits {
this.multiplicationInterval(x[0], y[1]),
this.multiplicationInterval(x[1], y[0])
);
- return [r0, r1, r2];
+
+ if (r0.isFinite() && r1.isFinite() && r2.isFinite()) {
+ return [r0, r1, r2];
+ }
+ return this.constants().unboundedVector[3];
},
};
@@ -3281,18 +3350,12 @@ export abstract class FPTraits {
? [this.toInterval([-(2 ** 126), -(2 ** -126)]), this.toInterval([2 ** -126, 2 ** 126])]
: [this.toInterval([-(2 ** 14), -(2 ** -14)]), this.toInterval([2 ** -14, 2 ** 14])];
return {
- impl: this.limitScalarPairToIntervalDomain(
- {
- x: domain_x,
- y: domain_y,
- },
- (x: number, y: number): FPInterval => {
- if (y === 0) {
- return constants.unboundedInterval;
- }
- return this.ulpInterval(x / y, 2.5);
+ impl: (x: number, y: number): FPInterval => {
+ if (y === 0) {
+ return constants.unboundedInterval;
}
- ),
+ return this.ulpInterval(x / y, 2.5);
+ },
extrema: (x: FPInterval, y: FPInterval): [FPInterval, FPInterval] => {
// division has a discontinuity at y = 0.
if (y.contains(0)) {
@@ -3300,6 +3363,9 @@ export abstract class FPTraits {
}
return [x, y];
},
+ domain: () => {
+ return { x: domain_x, y: domain_y };
+ },
};
}
@@ -3346,7 +3412,10 @@ export abstract class FPTraits {
x: readonly number[] | readonly FPInterval[],
y: readonly number[] | readonly FPInterval[]
): FPInterval {
- assert(x.length === y.length, `dot not defined for vectors with different lengths`);
+ assert(
+ x.length === y.length,
+ `dot not defined for vectors with different lengths, x = ${x}, y = ${y}`
+ );
return this.runVectorPairToIntervalOp(this.toVector(x), this.toVector(y), this.DotIntervalOp);
}
@@ -3517,12 +3586,12 @@ export abstract class FPTraits {
public abstract readonly fractInterval: (n: number) => FPInterval;
private readonly InverseSqrtIntervalOp: ScalarToIntervalOp = {
- impl: this.limitScalarToIntervalDomain(
- this.constants().greaterThanZeroInterval,
- (n: number): FPInterval => {
- return this.ulpInterval(1 / Math.sqrt(n), 2);
- }
- ),
+ impl: (n: number): FPInterval => {
+ return this.ulpInterval(1 / Math.sqrt(n), 2);
+ },
+ domain: () => {
+ return this.constants().greaterThanZeroInterval;
+ },
};
protected inverseSqrtIntervalImpl(n: number | FPInterval): FPInterval {
@@ -3534,21 +3603,19 @@ export abstract class FPTraits {
private readonly LdexpIntervalOp: ScalarPairToIntervalOp = {
impl: (e1: number, e2: number) => {
- assert(this.kind === 'f32' || this.kind === 'f16');
assert(Number.isInteger(e2), 'the second param of ldexp must be an integer');
- const bias = this.kind === 'f32' ? 127 : 15;
// Spec explicitly calls indeterminate value if e2 > bias + 1
- if (e2 > bias + 1) {
+ if (e2 > this.constants().bias + 1) {
return this.constants().unboundedInterval;
}
- // The spec says the result of ldexp(e1, e2) = e1 * 2 ^ e2, and the accuracy is correctly
- // rounded to the true value, so the inheritance framework does not need to be invoked to
- // determine bounds.
+ // The spec says the result of ldexp(e1, e2) = e1 * 2 ^ e2, and the
+ // accuracy is correctly rounded to the true value, so the inheritance
+ // framework does not need to be invoked to determine endpoints.
// Instead, the value at a higher precision is calculated and passed to
// correctlyRoundedInterval.
const result = e1 * 2 ** e2;
if (!Number.isFinite(result)) {
- // Overflowed TS's number type, so definitely out of bounds for f32/f16
+ // Overflowed TS's number type, so definitely out of bounds
return this.constants().unboundedInterval;
}
// The result may be zero if e2 + bias <= 0, but we can't simply span the interval to 0.0.
@@ -3606,17 +3673,17 @@ export abstract class FPTraits {
) => FPInterval;
private readonly LogIntervalOp: ScalarToIntervalOp = {
- impl: this.limitScalarToIntervalDomain(
- this.constants().greaterThanZeroInterval,
- (n: number): FPInterval => {
- assert(this.kind === 'f32' || this.kind === 'f16');
- const abs_error = this.kind === 'f32' ? 2 ** -21 : 2 ** -7;
- if (n >= 0.5 && n <= 2.0) {
- return this.absoluteErrorInterval(Math.log(n), abs_error);
- }
- return this.ulpInterval(Math.log(n), 3);
+ impl: (n: number): FPInterval => {
+ assert(this.kind === 'f32' || this.kind === 'f16');
+ const abs_error = this.kind === 'f32' ? 2 ** -21 : 2 ** -7;
+ if (n >= 0.5 && n <= 2.0) {
+ return this.absoluteErrorInterval(Math.log(n), abs_error);
}
- ),
+ return this.ulpInterval(Math.log(n), 3);
+ },
+ domain: () => {
+ return this.constants().greaterThanZeroInterval;
+ },
};
protected logIntervalImpl(x: number | FPInterval): FPInterval {
@@ -3627,17 +3694,17 @@ export abstract class FPTraits {
public abstract readonly logInterval: (x: number | FPInterval) => FPInterval;
private readonly Log2IntervalOp: ScalarToIntervalOp = {
- impl: this.limitScalarToIntervalDomain(
- this.constants().greaterThanZeroInterval,
- (n: number): FPInterval => {
- assert(this.kind === 'f32' || this.kind === 'f16');
- const abs_error = this.kind === 'f32' ? 2 ** -21 : 2 ** -7;
- if (n >= 0.5 && n <= 2.0) {
- return this.absoluteErrorInterval(Math.log2(n), abs_error);
- }
- return this.ulpInterval(Math.log2(n), 3);
+ impl: (n: number): FPInterval => {
+ assert(this.kind === 'f32' || this.kind === 'f16');
+ const abs_error = this.kind === 'f32' ? 2 ** -21 : 2 ** -7;
+ if (n >= 0.5 && n <= 2.0) {
+ return this.absoluteErrorInterval(Math.log2(n), abs_error);
}
- ),
+ return this.ulpInterval(Math.log2(n), 3);
+ },
+ domain: () => {
+ return this.constants().greaterThanZeroInterval;
+ },
};
protected log2IntervalImpl(x: number | FPInterval): FPInterval {
@@ -3791,14 +3858,10 @@ export abstract class FPTraits {
}
protected multiplicationMatrixScalarIntervalImpl(mat: Array2D<number>, scalar: number): FPMatrix {
- const cols = mat.length;
- const rows = mat[0].length;
- return this.toMatrix(
- unflatten2DArray(
- flatten2DArray(mat).map(e => this.multiplicationInterval(e, scalar)),
- cols,
- rows
- )
+ return this.runScalarPairToIntervalOpScalarMatrixComponentWise(
+ this.toInterval(scalar),
+ this.toMatrix(mat),
+ this.MultiplicationIntervalOp
);
}
@@ -3809,7 +3872,7 @@ export abstract class FPTraits {
) => FPMatrix;
protected multiplicationScalarMatrixIntervalImpl(scalar: number, mat: Array2D<number>): FPMatrix {
- return this.multiplicationMatrixScalarIntervalImpl(mat, scalar);
+ return this.multiplicationMatrixScalarInterval(mat, scalar);
}
/** Calculate an acceptance interval of x * y, when x is a scalar and y is a matrix */
@@ -3830,13 +3893,22 @@ export abstract class FPTraits {
const x_transposed = this.transposeInterval(mat_x);
+ let oob_result: boolean = false;
const result: FPInterval[][] = [...Array(y_cols)].map(_ => [...Array(x_rows)]);
mat_y.forEach((y, i) => {
x_transposed.forEach((x, j) => {
result[i][j] = this.dotInterval(x, y);
+ if (!oob_result && !result[i][j].isFinite()) {
+ oob_result = true;
+ }
});
});
+ if (oob_result) {
+ return this.constants().unboundedMatrix[result.length as 2 | 3 | 4][
+ result[0].length as 2 | 3 | 4
+ ];
+ }
return result as ROArrayArray<FPInterval> as FPMatrix;
}
@@ -3896,7 +3968,11 @@ export abstract class FPTraits {
private readonly NormalizeIntervalOp: VectorToVectorOp = {
impl: (n: readonly number[]): FPVector => {
const length = this.lengthInterval(n);
- return this.toVector(n.map(e => this.divisionInterval(e, length)));
+ const result = this.toVector(n.map(e => this.divisionInterval(e, length)));
+ if (result.some(r => !r.isFinite())) {
+ return this.constants().unboundedVector[result.length];
+ }
+ return result;
},
};
@@ -3955,11 +4031,16 @@ export abstract class FPTraits {
// y = normal of reflecting surface
const t = this.multiplicationInterval(2.0, this.dotInterval(x, y));
const rhs = this.multiplyVectorByScalar(y, t);
- return this.runScalarPairToIntervalOpVectorComponentWise(
+ const result = this.runScalarPairToIntervalOpVectorComponentWise(
this.toVector(x),
rhs,
this.SubtractionIntervalOp
);
+
+ if (result.some(r => !r.isFinite())) {
+ return this.constants().unboundedVector[result.length];
+ }
+ return result;
},
};
@@ -4015,11 +4096,16 @@ export abstract class FPTraits {
const k_sqrt = this.sqrtInterval(k);
const t = this.additionInterval(dot_times_r, k_sqrt); // t = r * dot(i, s) + sqrt(k)
- return this.runScalarPairToIntervalOpVectorComponentWise(
+ const result = this.runScalarPairToIntervalOpVectorComponentWise(
this.multiplyVectorByScalar(i, r),
this.multiplyVectorByScalar(s, t),
this.SubtractionIntervalOp
); // (i * r) - (s * t)
+
+ if (result.some(r => !r.isFinite())) {
+ return this.constants().unboundedVector[result.length];
+ }
+ return result;
}
/** Calculate acceptance interval vectors of reflect(i, s, r) */
@@ -4116,14 +4202,14 @@ export abstract class FPTraits {
public abstract readonly signInterval: (n: number) => FPInterval;
private readonly SinIntervalOp: ScalarToIntervalOp = {
- impl: this.limitScalarToIntervalDomain(
- this.constants().negPiToPiInterval,
- (n: number): FPInterval => {
- assert(this.kind === 'f32' || this.kind === 'f16');
- const abs_error = this.kind === 'f32' ? 2 ** -11 : 2 ** -7;
- return this.absoluteErrorInterval(Math.sin(n), abs_error);
- }
- ),
+ impl: (n: number): FPInterval => {
+ assert(this.kind === 'f32' || this.kind === 'f16');
+ const abs_error = this.kind === 'f32' ? 2 ** -11 : 2 ** -7;
+ return this.absoluteErrorInterval(Math.sin(n), abs_error);
+ },
+ domain: () => {
+ return this.constants().negPiToPiInterval;
+ },
};
protected sinIntervalImpl(n: number): FPInterval {
@@ -4250,7 +4336,7 @@ export abstract class FPTraits {
) => FPInterval;
protected subtractionMatrixMatrixIntervalImpl(x: Array2D<number>, y: Array2D<number>): FPMatrix {
- return this.runScalarPairToIntervalOpMatrixComponentWise(
+ return this.runScalarPairToIntervalOpMatrixMatrixComponentWise(
this.toMatrix(x),
this.toMatrix(y),
this.SubtractionIntervalOp
@@ -4375,6 +4461,7 @@ class F32Traits extends FPTraits {
sixth: kValue.f32.negative.pi.sixth,
},
},
+ bias: 127,
unboundedInterval: kF32UnboundedInterval,
zeroInterval: kF32ZeroInterval,
// Have to use the constants.ts values here, because values defined in the
@@ -4389,6 +4476,7 @@ class F32Traits extends FPTraits {
kValue.f32.positive.subnormal.min,
kValue.f32.positive.max
),
+ negOneToOneInterval: new FPInterval('f32', -1, 1),
zeroVector: {
2: [kF32ZeroInterval, kF32ZeroInterval],
3: [kF32ZeroInterval, kF32ZeroInterval, kF32ZeroInterval],
@@ -4520,6 +4608,11 @@ class F32Traits extends FPTraits {
public readonly flushSubnormal = flushSubnormalNumberF32;
public readonly oneULP = oneULPF32;
public readonly scalarBuilder = f32;
+ public readonly scalarRange = scalarF32Range;
+ public readonly sparseScalarRange = sparseScalarF32Range;
+ public readonly vectorRange = vectorF32Range;
+ public readonly sparseVectorRange = sparseVectorF32Range;
+ public readonly sparseMatrixRange = sparseMatrixF32Range;
// Framework - Fundamental Error Intervals - Overrides
public readonly absoluteErrorInterval = this.absoluteErrorIntervalImpl.bind(this);
@@ -4686,7 +4779,7 @@ class F32Traits extends FPTraits {
private unpack2x16floatIntervalImpl(n: number): FPVector {
assert(
n >= kValue.u32.min && n <= kValue.u32.max,
- 'unpack2x16floatInterval only accepts values on the bounds of u32'
+ 'unpack2x16floatInterval only accepts valid u32 values'
);
this.unpackDataU32[0] = n;
if (this.unpackDataF16.some(f => !isFiniteF16(f))) {
@@ -4710,7 +4803,7 @@ class F32Traits extends FPTraits {
private unpack2x16snormIntervalImpl(n: number): FPVector {
assert(
n >= kValue.u32.min && n <= kValue.u32.max,
- 'unpack2x16snormInterval only accepts values on the bounds of u32'
+ 'unpack2x16snormInterval only accepts valid u32 values'
);
const op = (n: number): FPInterval => {
return this.ulpInterval(Math.max(n / 32767, -1), 3);
@@ -4726,7 +4819,7 @@ class F32Traits extends FPTraits {
private unpack2x16unormIntervalImpl(n: number): FPVector {
assert(
n >= kValue.u32.min && n <= kValue.u32.max,
- 'unpack2x16unormInterval only accepts values on the bounds of u32'
+ 'unpack2x16unormInterval only accepts valid u32 values'
);
const op = (n: number): FPInterval => {
return this.ulpInterval(n / 65535, 3);
@@ -4742,7 +4835,7 @@ class F32Traits extends FPTraits {
private unpack4x8snormIntervalImpl(n: number): FPVector {
assert(
n >= kValue.u32.min && n <= kValue.u32.max,
- 'unpack4x8snormInterval only accepts values on the bounds of u32'
+ 'unpack4x8snormInterval only accepts valid u32 values'
);
const op = (n: number): FPInterval => {
return this.ulpInterval(Math.max(n / 127, -1), 3);
@@ -4762,7 +4855,7 @@ class F32Traits extends FPTraits {
private unpack4x8unormIntervalImpl(n: number): FPVector {
assert(
n >= kValue.u32.min && n <= kValue.u32.max,
- 'unpack4x8unormInterval only accepts values on the bounds of u32'
+ 'unpack4x8unormInterval only accepts valid u32 values'
);
const op = (n: number): FPInterval => {
return this.ulpInterval(n / 255, 3);
@@ -4836,6 +4929,7 @@ class FPAbstractTraits extends FPTraits {
sixth: kValue.f64.negative.pi.sixth,
},
},
+ bias: 1023,
unboundedInterval: kAbstractUnboundedInterval,
zeroInterval: kAbstractZeroInterval,
// Have to use the constants.ts values here, because values defined in the
@@ -4850,6 +4944,8 @@ class FPAbstractTraits extends FPTraits {
kValue.f64.positive.subnormal.min,
kValue.f64.positive.max
),
+ negOneToOneInterval: new FPInterval('abstract', -1, 1),
+
zeroVector: {
2: [kAbstractZeroInterval, kAbstractZeroInterval],
3: [kAbstractZeroInterval, kAbstractZeroInterval, kAbstractZeroInterval],
@@ -4992,14 +5088,17 @@ class FPAbstractTraits extends FPTraits {
unreachable(`'FPAbstractTraits.oneULP should never be called`);
};
public readonly scalarBuilder = abstractFloat;
+ public readonly scalarRange = scalarF64Range;
+ public readonly sparseScalarRange = sparseScalarF64Range;
+ public readonly vectorRange = vectorF64Range;
+ public readonly sparseVectorRange = sparseVectorF64Range;
+ public readonly sparseMatrixRange = sparseMatrixF64Range;
// Framework - Fundamental Error Intervals - Overrides
- public readonly absoluteErrorInterval = this.unboundedAbsoluteErrorInterval.bind(this);
+ public readonly absoluteErrorInterval = this.unimplementedAbsoluteErrorInterval.bind(this); // Should use FP.f32 instead
public readonly correctlyRoundedInterval = this.correctlyRoundedIntervalImpl.bind(this);
public readonly correctlyRoundedMatrix = this.correctlyRoundedMatrixImpl.bind(this);
- public readonly ulpInterval = (n: number, numULP: number): FPInterval => {
- return this.toInterval(kF32Traits.ulpInterval(n, numULP));
- };
+ public readonly ulpInterval = this.unimplementedUlpInterval.bind(this); // Should use FP.f32 instead
// Framework - API - Overrides
public readonly absInterval = this.absIntervalImpl.bind(this);
@@ -5023,40 +5122,38 @@ class FPAbstractTraits extends FPTraits {
'atan2Interval'
);
public readonly atanhInterval = this.unimplementedScalarToInterval.bind(this, 'atanhInterval');
- public readonly ceilInterval = this.unimplementedScalarToInterval.bind(this, 'ceilInterval');
+ public readonly ceilInterval = this.ceilIntervalImpl.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.unimplementedScalarToInterval.bind(this, 'cosInterval');
public readonly coshInterval = this.unimplementedScalarToInterval.bind(this, 'coshInterval');
- public readonly crossInterval = this.crossIntervalImpl.bind(this);
- public readonly degreesInterval = this.degreesIntervalImpl.bind(this);
+ public readonly crossInterval = this.unimplementedVectorPairToVector.bind(this, 'crossInterval');
+ public readonly degreesInterval = this.unimplementedScalarToInterval.bind(
+ this,
+ 'degreesInterval'
+ );
public readonly determinantInterval = this.unimplementedMatrixToInterval.bind(
this,
- 'determinantInterval'
+ 'determinant'
);
public readonly distanceInterval = this.unimplementedDistance.bind(this);
- public readonly divisionInterval = (
- x: number | FPInterval,
- y: number | FPInterval
- ): FPInterval => {
- return this.toInterval(kF32Traits.divisionInterval(x, y));
- };
+ public readonly divisionInterval = this.unimplementedScalarPairToInterval.bind(
+ this,
+ 'divisionInterval'
+ );
public readonly dotInterval = this.unimplementedVectorPairToInterval.bind(this, 'dotInterval');
public readonly expInterval = this.unimplementedScalarToInterval.bind(this, 'expInterval');
public readonly exp2Interval = this.unimplementedScalarToInterval.bind(this, 'exp2Interval');
public readonly faceForwardIntervals = this.unimplementedFaceForward.bind(this);
public readonly floorInterval = this.floorIntervalImpl.bind(this);
- public readonly fmaInterval = this.fmaIntervalImpl.bind(this);
- public readonly fractInterval = this.unimplementedScalarToInterval.bind(this, 'fractInterval');
+ public readonly fmaInterval = this.unimplementedScalarTripleToInterval.bind(this, 'fmaInterval');
+ public readonly fractInterval = this.fractIntervalImpl.bind(this);
public readonly inverseSqrtInterval = this.unimplementedScalarToInterval.bind(
this,
'inverseSqrtInterval'
);
- public readonly ldexpInterval = this.unimplementedScalarPairToInterval.bind(
- this,
- 'ldexpInterval'
- );
+ public readonly ldexpInterval = this.ldexpIntervalImpl.bind(this);
public readonly lengthInterval = this.unimplementedLength.bind(this);
public readonly logInterval = this.unimplementedScalarToInterval.bind(this, 'logInterval');
public readonly log2Interval = this.unimplementedScalarToInterval.bind(this, 'log2Interval');
@@ -5077,14 +5174,10 @@ class FPAbstractTraits extends FPTraits {
this,
'multiplicationMatrixMatrixInterval'
);
- public readonly multiplicationMatrixScalarInterval = this.unimplementedMatrixScalarToMatrix.bind(
- this,
- 'multiplicationMatrixScalarInterval'
- );
- public readonly multiplicationScalarMatrixInterval = this.unimplementedScalarMatrixToMatrix.bind(
- this,
- 'multiplicationScalarMatrixInterval'
- );
+ public readonly multiplicationMatrixScalarInterval =
+ this.multiplicationMatrixScalarIntervalImpl.bind(this);
+ public readonly multiplicationScalarMatrixInterval =
+ this.multiplicationScalarMatrixIntervalImpl.bind(this);
public readonly multiplicationMatrixVectorInterval = this.unimplementedMatrixVectorToVector.bind(
this,
'multiplicationMatrixVectorInterval'
@@ -5099,16 +5192,17 @@ class FPAbstractTraits extends FPTraits {
'normalizeInterval'
);
public readonly powInterval = this.unimplementedScalarPairToInterval.bind(this, 'powInterval');
- public readonly radiansInterval = this.radiansIntervalImpl.bind(this);
+ public readonly radiansInterval = this.unimplementedScalarToInterval.bind(this, 'radiansImpl');
public readonly reflectInterval = this.unimplementedVectorPairToVector.bind(
this,
'reflectInterval'
);
public readonly refractInterval = this.unimplementedRefract.bind(this);
- public readonly remainderInterval = (x: number, y: number): FPInterval => {
- return this.toInterval(kF32Traits.remainderInterval(x, y));
- };
- public readonly roundInterval = this.unimplementedScalarToInterval.bind(this, 'roundInterval');
+ public readonly remainderInterval = this.unimplementedScalarPairToInterval.bind(
+ this,
+ 'remainderInterval'
+ );
+ public readonly roundInterval = this.roundIntervalImpl.bind(this);
public readonly saturateInterval = this.saturateIntervalImpl.bind(this);
public readonly signInterval = this.signIntervalImpl.bind(this);
public readonly sinInterval = this.unimplementedScalarToInterval.bind(this, 'sinInterval');
@@ -5118,7 +5212,7 @@ class FPAbstractTraits extends FPTraits {
'smoothStepInterval'
);
public readonly sqrtInterval = this.unimplementedScalarToInterval.bind(this, 'sqrtInterval');
- public readonly stepInterval = this.unimplementedScalarPairToInterval.bind(this, 'stepInterval');
+ public readonly stepInterval = this.stepIntervalImpl.bind(this);
public readonly subtractionInterval = this.subtractionIntervalImpl.bind(this);
public readonly subtractionMatrixMatrixInterval =
this.subtractionMatrixMatrixIntervalImpl.bind(this);
@@ -5179,6 +5273,7 @@ class F16Traits extends FPTraits {
sixth: kValue.f16.negative.pi.sixth,
},
},
+ bias: 15,
unboundedInterval: kF16UnboundedInterval,
zeroInterval: kF16ZeroInterval,
// Have to use the constants.ts values here, because values defined in the
@@ -5193,6 +5288,8 @@ class F16Traits extends FPTraits {
kValue.f16.positive.subnormal.min,
kValue.f16.positive.max
),
+ negOneToOneInterval: new FPInterval('f16', -1, 1),
+
zeroVector: {
2: [kF16ZeroInterval, kF16ZeroInterval],
3: [kF16ZeroInterval, kF16ZeroInterval, kF16ZeroInterval],
@@ -5324,6 +5421,11 @@ class F16Traits extends FPTraits {
public readonly flushSubnormal = flushSubnormalNumberF16;
public readonly oneULP = oneULPF16;
public readonly scalarBuilder = f16;
+ public readonly scalarRange = scalarF16Range;
+ public readonly sparseScalarRange = sparseScalarF16Range;
+ public readonly vectorRange = vectorF16Range;
+ public readonly sparseVectorRange = sparseVectorF16Range;
+ public readonly sparseMatrixRange = sparseMatrixF16Range;
// Framework - Fundamental Error Intervals - Overrides
public readonly absoluteErrorInterval = this.absoluteErrorIntervalImpl.bind(this);
@@ -5437,5 +5539,6 @@ export function isRepresentable(value: number, type: ScalarType) {
const constants = fpTraitsFor(type).constants();
return value >= constants.negative.min && value <= constants.positive.max;
}
+
assert(false, `isRepresentable() is not yet implemented for type ${type}`);
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/math.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/math.ts
index 851db40c71..20d7818df6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/math.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/math.ts
@@ -414,6 +414,25 @@ export function oneULPF32(target: number, mode: FlushMode = 'flush'): number {
}
/**
+ * @returns an integer value between 0..0xffffffff using a simple non-cryptographic hash function
+ * @param values integers to generate hash from.
+ */
+export function hashU32(...values: number[]) {
+ let n = 0x3504_f333;
+ for (const v of values) {
+ n = v + (n << 7) + (n >>> 1);
+ n = (n * 0x29493) & 0xffff_ffff;
+ }
+ n ^= n >>> 8;
+ n += n << 15;
+ n = n & 0xffff_ffff;
+ if (n < 0) {
+ n = ~n * 2 + 1;
+ }
+ return n;
+}
+
+/**
* @returns ulp(x), the unit of least precision for a specific number as a 32-bit float
*
* ulp(x) is the distance between the two floating point numbers nearest x.
@@ -881,6 +900,41 @@ export function biasedRange(a: number, b: number, num_steps: number): readonly n
}
/**
+ * Version of biasedRange that operates on bigint values
+ *
+ * biasedRange was not made into a generic or to take in (number|bigint),
+ * because that introduces a bunch of complexity overhead related to type
+ * differentiation.
+ *
+ * Scaling is used internally so that the number of possible indices is
+ * significantly larger than num_steps. This is done to avoid duplicate entries
+ * in the resulting range due to quantizing to integers during the calculation.
+ *
+ * If a and b are close together, such that the number of integers between them
+ * is close to num_steps, then duplicates will occur regardless of scaling.
+ */
+export function biasedRangeBigInt(a: bigint, b: bigint, num_steps: number): readonly bigint[] {
+ if (num_steps <= 0) {
+ return [];
+ }
+
+ // Avoid division by 0
+ if (num_steps === 1) {
+ return [a];
+ }
+
+ const c = 2;
+ const scaling = 1000;
+ const scaled_num_steps = num_steps * scaling;
+
+ return Array.from(Array(num_steps).keys()).map(i => {
+ const biased_i = Math.pow(i / (num_steps - 1), c); // Floating Point on [0, 1]
+ const scaled_i = Math.trunc((scaled_num_steps - 1) * biased_i); // Integer on [0, scaled_num_steps - 1]
+ return lerpBigInt(a, b, scaled_i, scaled_num_steps);
+ });
+}
+
+/**
* @returns an ascending sorted array of numbers spread over the entire range of 32-bit floats
*
* Numbers are divided into 4 regions: negative normals, negative subnormals, positive subnormals & positive normals.
@@ -891,12 +945,12 @@ export function biasedRange(a: number, b: number, num_steps: number): readonly n
* for a wide range of magnitudes to be generated, instead of being extremely biased towards the edges of the f32 range.
*
* This function is intended to provide dense coverage of the f32 range, for a minimal list of values to use to cover
- * f32 behaviour, use sparseF32Range instead.
+ * f32 behaviour, use sparseScalarF32Range instead.
*
* @param counts structure param with 4 entries indicating the number of entries to be generated each region, entries
* must be 0 or greater.
*/
-export function fullF32Range(
+export function scalarF32Range(
counts: {
neg_norm?: number;
neg_sub?: number;
@@ -943,8 +997,8 @@ export function fullF32Range(
* @param low the lowest f32 value to permit when filtered
* @param high the highest f32 value to permit when filtered
*/
-export function sourceFilteredF32Range(source: String, low: number, high: number): Array<number> {
- return fullF32Range().filter(x => source !== 'const' || (x >= low && x <= high));
+export function filteredScalarF32Range(source: String, low: number, high: number): Array<number> {
+ return scalarF32Range().filter(x => source !== 'const' || (x >= low && x <= high));
}
/**
@@ -958,12 +1012,12 @@ export function sourceFilteredF32Range(source: String, low: number, high: number
* for a wide range of magnitudes to be generated, instead of being extremely biased towards the edges of the f16 range.
*
* This function is intended to provide dense coverage of the f16 range, for a minimal list of values to use to cover
- * f16 behaviour, use sparseF16Range instead.
+ * f16 behaviour, use sparseScalarF16Range instead.
*
* @param counts structure param with 4 entries indicating the number of entries to be generated each region, entries
* must be 0 or greater.
*/
-export function fullF16Range(
+export function scalarF16Range(
counts: {
neg_norm?: number;
neg_sub?: number;
@@ -1009,12 +1063,12 @@ export function fullF16Range(
* for a wide range of magnitudes to be generated, instead of being extremely biased towards the edges of the f64 range.
*
* This function is intended to provide dense coverage of the f64 range, for a minimal list of values to use to cover
- * f64 behaviour, use sparseF64Range instead.
+ * f64 behaviour, use sparseScalarF64Range instead.
*
* @param counts structure param with 4 entries indicating the number of entries to be generated each region, entries
* must be 0 or greater.
*/
-export function fullF64Range(
+export function scalarF64Range(
counts: {
neg_norm?: number;
neg_sub?: number;
@@ -1065,7 +1119,7 @@ export function fullF64Range(
* @param counts structure param with 4 entries indicating the number of entries
* to be generated each region, entries must be 0 or greater.
*/
-export function filteredF64Range(
+export function limitedScalarF64Range(
begin: number,
end: number,
counts: { neg_norm?: number; neg_sub?: number; pos_sub: number; pos_norm: number } = {
@@ -1136,27 +1190,18 @@ export function sparseI32Range(): readonly number[] {
const kVectorI32Values = {
2: kInterestingI32Values.flatMap(f => [
[f, 1],
- [1, f],
- [f, -1],
[-1, f],
]),
3: kInterestingI32Values.flatMap(f => [
- [f, 1, 2],
- [1, f, 2],
- [1, 2, f],
- [f, -1, -2],
- [-1, f, -2],
- [-1, -2, f],
+ [f, 1, -2],
+ [-1, f, 2],
+ [1, -2, f],
]),
4: kInterestingI32Values.flatMap(f => [
- [f, 1, 2, 3],
- [1, f, 2, 3],
- [1, 2, f, 3],
- [1, 2, 3, f],
- [f, -1, -2, -3],
- [-1, f, -2, -3],
- [-1, -2, f, -3],
- [-1, -2, -3, f],
+ [f, -1, 2, 3],
+ [1, f, -2, 3],
+ [1, 2, f, -3],
+ [-1, 2, -3, f],
]),
};
@@ -1178,6 +1223,38 @@ export function vectorI32Range(dim: number): ROArrayArray<number> {
return kVectorI32Values[dim];
}
+const kSparseVectorI32Values = {
+ 2: sparseI32Range().map((i, idx) => [idx % 2 === 0 ? i : idx, idx % 2 === 1 ? i : -idx]),
+ 3: sparseI32Range().map((i, idx) => [
+ idx % 3 === 0 ? i : idx,
+ idx % 3 === 1 ? i : -idx,
+ idx % 3 === 2 ? i : idx,
+ ]),
+ 4: sparseI32Range().map((i, idx) => [
+ idx % 4 === 0 ? i : idx,
+ idx % 4 === 1 ? i : -idx,
+ idx % 4 === 2 ? i : idx,
+ idx % 4 === 3 ? i : -idx,
+ ]),
+};
+
+/**
+ * Minimal set of vectors, indexed by dimension, that contain interesting
+ * abstract integer values.
+ *
+ * This is an even more stripped down version of `vectorI32Range` for when
+ * pairs of vectors are being tested.
+ * All interesting integers from sparseI32Range are guaranteed to be
+ * tested, but not in every position.
+ */
+export function sparseVectorI32Range(dim: number): ROArrayArray<number> {
+ assert(
+ dim === 2 || dim === 3 || dim === 4,
+ 'sparseVectorI32Range only accepts dimensions 2, 3, and 4'
+ );
+ return kSparseVectorI32Values[dim];
+}
+
/**
* @returns an ascending sorted array of numbers spread over the entire range of 32-bit signed ints
*
@@ -1256,6 +1333,38 @@ export function vectorU32Range(dim: number): ROArrayArray<number> {
return kVectorU32Values[dim];
}
+const kSparseVectorU32Values = {
+ 2: sparseU32Range().map((i, idx) => [idx % 2 === 0 ? i : idx, idx % 2 === 1 ? i : -idx]),
+ 3: sparseU32Range().map((i, idx) => [
+ idx % 3 === 0 ? i : idx,
+ idx % 3 === 1 ? i : -idx,
+ idx % 3 === 2 ? i : idx,
+ ]),
+ 4: sparseU32Range().map((i, idx) => [
+ idx % 4 === 0 ? i : idx,
+ idx % 4 === 1 ? i : -idx,
+ idx % 4 === 2 ? i : idx,
+ idx % 4 === 3 ? i : -idx,
+ ]),
+};
+
+/**
+ * Minimal set of vectors, indexed by dimension, that contain interesting
+ * abstract integer values.
+ *
+ * This is an even more stripped down version of `vectorU32Range` for when
+ * pairs of vectors are being tested.
+ * All interesting integers from sparseU32Range are guaranteed to be
+ * tested, but not in every position.
+ */
+export function sparseVectorU32Range(dim: number): ROArrayArray<number> {
+ assert(
+ dim === 2 || dim === 3 || dim === 4,
+ 'sparseVectorU32Range only accepts dimensions 2, 3, and 4'
+ );
+ return kSparseVectorU32Values[dim];
+}
+
/**
* @returns an ascending sorted array of numbers spread over the entire range of 32-bit unsigned ints
*
@@ -1267,6 +1376,124 @@ export function fullU32Range(count: number = 50): Array<number> {
return [0, ...biasedRange(1, kValue.u32.max, count)].map(Math.trunc);
}
+/** Short list of i64 values of interest to test against */
+const kInterestingI64Values: readonly bigint[] = [
+ kValue.i64.negative.max,
+ kValue.i64.negative.max / 2n,
+ -256n,
+ -10n,
+ -1n,
+ 0n,
+ 1n,
+ 10n,
+ 256n,
+ kValue.i64.positive.max / 2n,
+ kValue.i64.positive.max,
+];
+
+/** @returns minimal i64 values that cover the entire range of i64 behaviours
+ *
+ * This is used instead of fullI64Range when the number of test cases being
+ * generated is a super linear function of the length of i64 values which is
+ * leading to time outs.
+ */
+export function sparseI64Range(): readonly bigint[] {
+ return kInterestingI64Values;
+}
+
+const kVectorI64Values = {
+ 2: kInterestingI64Values.flatMap(f => [
+ [f, 1n],
+ [-1n, f],
+ ]),
+ 3: kInterestingI64Values.flatMap(f => [
+ [f, 1n, -2n],
+ [-1n, f, 2n],
+ [1n, -2n, f],
+ ]),
+ 4: kInterestingI64Values.flatMap(f => [
+ [f, -1n, 2n, 3n],
+ [1n, f, -2n, 3n],
+ [1n, 2n, f, -3n],
+ [-1n, 2n, -3n, f],
+ ]),
+};
+
+/**
+ * Returns set of vectors, indexed by dimension containing interesting i64
+ * values.
+ *
+ * The tests do not do the simple option for coverage of computing the cartesian
+ * product of all of the interesting i64 values N times for vecN tests,
+ * because that creates a huge number of tests for vec3 and vec4, leading to
+ * time outs.
+ *
+ * Instead they insert the interesting i64 values into each location of the
+ * vector to get a spread of testing over the entire range. This reduces the
+ * number of cases being run substantially, but maintains coverage.
+ */
+export function vectorI64Range(dim: number): ROArrayArray<bigint> {
+ assert(dim === 2 || dim === 3 || dim === 4, 'vectorI64Range only accepts dimensions 2, 3, and 4');
+ return kVectorI64Values[dim];
+}
+
+const kSparseVectorI64Values = {
+ 2: sparseI64Range().map((i, idx) => [
+ idx % 2 === 0 ? i : BigInt(idx),
+ idx % 2 === 1 ? i : -BigInt(idx),
+ ]),
+ 3: sparseI64Range().map((i, idx) => [
+ idx % 3 === 0 ? i : BigInt(idx),
+ idx % 3 === 1 ? i : -BigInt(idx),
+ idx % 3 === 2 ? i : BigInt(idx),
+ ]),
+ 4: sparseI64Range().map((i, idx) => [
+ idx % 4 === 0 ? i : BigInt(idx),
+ idx % 4 === 1 ? i : -BigInt(idx),
+ idx % 4 === 2 ? i : BigInt(idx),
+ idx % 4 === 3 ? i : -BigInt(idx),
+ ]),
+};
+
+/**
+ * Minimal set of vectors, indexed by dimension, that contain interesting
+ * abstract integer values.
+ *
+ * This is an even more stripped down version of `vectorI64Range` for when
+ * pairs of vectors are being tested.
+ * All interesting integers from sparseI64Range are guaranteed to be
+ * tested, but not in every position.
+ */
+export function sparseVectorI64Range(dim: number): ROArrayArray<bigint> {
+ assert(
+ dim === 2 || dim === 3 || dim === 4,
+ 'sparseVectorI64Range only accepts dimensions 2, 3, and 4'
+ );
+ return kSparseVectorI64Values[dim];
+}
+
+/**
+ * @returns an ascending sorted array of numbers spread over the entire range of 64-bit signed ints
+ *
+ * Numbers are divided into 2 regions: negatives, and positives, with their spreads biased towards 0
+ * Zero is included in range.
+ *
+ * @param counts structure param with 2 entries indicating the number of entries to be generated each region, values must be 0 or greater.
+ */
+export function fullI64Range(
+ counts: {
+ negative?: number;
+ positive: number;
+ } = { positive: 50 }
+): Array<bigint> {
+ counts.negative = counts.negative === undefined ? counts.positive : counts.negative;
+ return [
+ ...biasedRangeBigInt(kValue.i64.negative.min, -1n, counts.negative),
+ 0n,
+ ...biasedRangeBigInt(1n, kValue.i64.positive.max, counts.positive),
+ ];
+}
+
/** Short list of f32 values of interest to test against */
const kInterestingF32Values: readonly number[] = [
kValue.f32.negative.min,
@@ -1299,34 +1526,25 @@ const kInterestingF32Values: readonly number[] = [
* specific values of interest. If there are known values of interest they
* should be appended to this list in the test generation code.
*/
-export function sparseF32Range(): readonly number[] {
+export function sparseScalarF32Range(): readonly number[] {
return kInterestingF32Values;
}
const kVectorF32Values = {
- 2: sparseF32Range().flatMap(f => [
+ 2: kInterestingF32Values.flatMap(f => [
[f, 1.0],
- [1.0, f],
- [f, -1.0],
[-1.0, f],
]),
- 3: sparseF32Range().flatMap(f => [
- [f, 1.0, 2.0],
- [1.0, f, 2.0],
- [1.0, 2.0, f],
- [f, -1.0, -2.0],
- [-1.0, f, -2.0],
- [-1.0, -2.0, f],
+ 3: kInterestingF32Values.flatMap(f => [
+ [f, 1.0, -2.0],
+ [-1.0, f, 2.0],
+ [1.0, -2.0, f],
]),
- 4: sparseF32Range().flatMap(f => [
- [f, 1.0, 2.0, 3.0],
- [1.0, f, 2.0, 3.0],
- [1.0, 2.0, f, 3.0],
- [1.0, 2.0, 3.0, f],
- [f, -1.0, -2.0, -3.0],
- [-1.0, f, -2.0, -3.0],
- [-1.0, -2.0, f, -3.0],
- [-1.0, -2.0, -3.0, f],
+ 4: kInterestingF32Values.flatMap(f => [
+ [f, -1.0, 2.0, 3.0],
+ [1.0, f, -2.0, 3.0],
+ [1.0, 2.0, f, -3.0],
+ [-1.0, 2.0, -3.0, f],
]),
};
@@ -1349,13 +1567,13 @@ export function vectorF32Range(dim: number): ROArrayArray<number> {
}
const kSparseVectorF32Values = {
- 2: sparseF32Range().map((f, idx) => [idx % 2 === 0 ? f : idx, idx % 2 === 1 ? f : -idx]),
- 3: sparseF32Range().map((f, idx) => [
+ 2: sparseScalarF32Range().map((f, idx) => [idx % 2 === 0 ? f : idx, idx % 2 === 1 ? f : -idx]),
+ 3: sparseScalarF32Range().map((f, idx) => [
idx % 3 === 0 ? f : idx,
idx % 3 === 1 ? f : -idx,
idx % 3 === 2 ? f : idx,
]),
- 4: sparseF32Range().map((f, idx) => [
+ 4: sparseScalarF32Range().map((f, idx) => [
idx % 4 === 0 ? f : idx,
idx % 4 === 1 ? f : -idx,
idx % 4 === 2 ? f : idx,
@@ -1369,8 +1587,8 @@ const kSparseVectorF32Values = {
*
* This is an even more stripped down version of `vectorF32Range` for when
* pairs of vectors are being tested.
- * All of the interesting floats from sparseF32 are guaranteed to be tested, but
- * not in every position.
+ * All of the interesting floats from sparseScalarF32 are guaranteed to be
+ * tested, but not in every position.
*/
export function sparseVectorF32Range(dim: number): ROArrayArray<number> {
assert(
@@ -1480,16 +1698,16 @@ const kSparseMatrixF32Values = {
};
/**
- * Returns a minimal set of matrices, indexed by dimension containing interesting
- * float values.
+ * Returns a minimal set of matrices, indexed by dimension containing
+ * interesting float values.
*
* This is the matrix analogue of `sparseVectorF32Range`, so it is producing a
* minimal coverage set of matrices that test all of the interesting f32 values.
* There is not a more expansive set of matrices, since matrices are even more
* expensive than vectors for increasing runtime with coverage.
*
- * All of the interesting floats from sparseF32 are guaranteed to be tested, but
- * not in every position.
+ * All of the interesting floats from sparseScalarF32 are guaranteed to be
+ * tested, but not in every position.
*/
export function sparseMatrixF32Range(c: number, r: number): ROArrayArrayArray<number> {
assert(
@@ -1535,34 +1753,25 @@ const kInterestingF16Values: readonly number[] = [
* specific values of interest. If there are known values of interest they
* should be appended to this list in the test generation code.
*/
-export function sparseF16Range(): readonly number[] {
+export function sparseScalarF16Range(): readonly number[] {
return kInterestingF16Values;
}
const kVectorF16Values = {
- 2: sparseF16Range().flatMap(f => [
+ 2: kInterestingF16Values.flatMap(f => [
[f, 1.0],
- [1.0, f],
- [f, -1.0],
[-1.0, f],
]),
- 3: sparseF16Range().flatMap(f => [
- [f, 1.0, 2.0],
- [1.0, f, 2.0],
- [1.0, 2.0, f],
- [f, -1.0, -2.0],
- [-1.0, f, -2.0],
- [-1.0, -2.0, f],
+ 3: kInterestingF16Values.flatMap(f => [
+ [f, 1.0, -2.0],
+ [-1.0, f, 2.0],
+ [1.0, -2.0, f],
]),
- 4: sparseF16Range().flatMap(f => [
- [f, 1.0, 2.0, 3.0],
- [1.0, f, 2.0, 3.0],
- [1.0, 2.0, f, 3.0],
- [1.0, 2.0, 3.0, f],
- [f, -1.0, -2.0, -3.0],
- [-1.0, f, -2.0, -3.0],
- [-1.0, -2.0, f, -3.0],
- [-1.0, -2.0, -3.0, f],
+ 4: kInterestingF16Values.flatMap(f => [
+ [f, -1.0, 2.0, 3.0],
+ [1.0, f, -2.0, 3.0],
+ [1.0, 2.0, f, -3.0],
+ [-1.0, 2.0, -3.0, f],
]),
};
@@ -1585,13 +1794,13 @@ export function vectorF16Range(dim: number): ROArrayArray<number> {
}
const kSparseVectorF16Values = {
- 2: sparseF16Range().map((f, idx) => [idx % 2 === 0 ? f : idx, idx % 2 === 1 ? f : -idx]),
- 3: sparseF16Range().map((f, idx) => [
+ 2: sparseScalarF16Range().map((f, idx) => [idx % 2 === 0 ? f : idx, idx % 2 === 1 ? f : -idx]),
+ 3: sparseScalarF16Range().map((f, idx) => [
idx % 3 === 0 ? f : idx,
idx % 3 === 1 ? f : -idx,
idx % 3 === 2 ? f : idx,
]),
- 4: sparseF16Range().map((f, idx) => [
+ 4: sparseScalarF16Range().map((f, idx) => [
idx % 4 === 0 ? f : idx,
idx % 4 === 1 ? f : -idx,
idx % 4 === 2 ? f : idx,
@@ -1605,8 +1814,8 @@ const kSparseVectorF16Values = {
*
* This is an even more stripped down version of `vectorF16Range` for when
* pairs of vectors are being tested.
- * All of the interesting floats from sparseF16 are guaranteed to be tested, but
- * not in every position.
+ * All of the interesting floats from sparseScalarF16 are guaranteed to be
+ * tested, but not in every position.
*/
export function sparseVectorF16Range(dim: number): ROArrayArray<number> {
assert(
@@ -1724,7 +1933,7 @@ const kSparseMatrixF16Values = {
* There is not a more expansive set of matrices, since matrices are even more
* expensive than vectors for increasing runtime with coverage.
*
- * All of the interesting floats from sparseF16 are guaranteed to be tested, but
+ * All of the interesting floats from sparseScalarF16 are guaranteed to be tested, but
* not in every position.
*/
export function sparseMatrixF16Range(c: number, r: number): ROArrayArray<number>[] {
@@ -1771,34 +1980,25 @@ const kInterestingF64Values: readonly number[] = [
* specific values of interest. If there are known values of interest they
* should be appended to this list in the test generation code.
*/
-export function sparseF64Range(): readonly number[] {
+export function sparseScalarF64Range(): readonly number[] {
return kInterestingF64Values;
}
const kVectorF64Values = {
- 2: sparseF64Range().flatMap(f => [
+ 2: kInterestingF64Values.flatMap(f => [
[f, 1.0],
- [1.0, f],
- [f, -1.0],
[-1.0, f],
]),
- 3: sparseF64Range().flatMap(f => [
- [f, 1.0, 2.0],
- [1.0, f, 2.0],
- [1.0, 2.0, f],
- [f, -1.0, -2.0],
- [-1.0, f, -2.0],
- [-1.0, -2.0, f],
+ 3: kInterestingF64Values.flatMap(f => [
+ [f, 1.0, -2.0],
+ [-1.0, f, 2.0],
+ [1.0, -2.0, f],
]),
- 4: sparseF64Range().flatMap(f => [
- [f, 1.0, 2.0, 3.0],
- [1.0, f, 2.0, 3.0],
- [1.0, 2.0, f, 3.0],
- [1.0, 2.0, 3.0, f],
- [f, -1.0, -2.0, -3.0],
- [-1.0, f, -2.0, -3.0],
- [-1.0, -2.0, f, -3.0],
- [-1.0, -2.0, -3.0, f],
+ 4: kInterestingF64Values.flatMap(f => [
+ [f, -1.0, 2.0, 3.0],
+ [1.0, f, -2.0, 3.0],
+ [1.0, 2.0, f, -3.0],
+ [-1.0, 2.0, -3.0, f],
]),
};
@@ -1821,13 +2021,13 @@ export function vectorF64Range(dim: number): ROArrayArray<number> {
}
const kSparseVectorF64Values = {
- 2: sparseF64Range().map((f, idx) => [idx % 2 === 0 ? f : idx, idx % 2 === 1 ? f : -idx]),
- 3: sparseF64Range().map((f, idx) => [
+ 2: sparseScalarF64Range().map((f, idx) => [idx % 2 === 0 ? f : idx, idx % 2 === 1 ? f : -idx]),
+ 3: sparseScalarF64Range().map((f, idx) => [
idx % 3 === 0 ? f : idx,
idx % 3 === 1 ? f : -idx,
idx % 3 === 2 ? f : idx,
]),
- 4: sparseF64Range().map((f, idx) => [
+ 4: sparseScalarF64Range().map((f, idx) => [
idx % 4 === 0 ? f : idx,
idx % 4 === 1 ? f : -idx,
idx % 4 === 2 ? f : idx,
@@ -1841,7 +2041,7 @@ const kSparseVectorF64Values = {
*
* 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
+ * All the interesting floats from sparseScalarF64 are guaranteed to be tested, but
* not in every position.
*/
export function sparseVectorF64Range(dim: number): ROArrayArray<number> {
@@ -1960,19 +2160,19 @@ const kSparseMatrixF64Values = {
* 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
+ * All the interesting floats from sparseScalarF64 are guaranteed to be tested, but
* not in every position.
*/
-export function sparseMatrixF64Range(c: number, r: number): ROArrayArray<number>[] {
+export function sparseMatrixF64Range(cols: number, rows: number): ROArrayArrayArray<number> {
assert(
- c === 2 || c === 3 || c === 4,
+ cols === 2 || cols === 3 || cols === 4,
'sparseMatrixF64Range only accepts column counts of 2, 3, and 4'
);
assert(
- r === 2 || r === 3 || r === 4,
+ rows === 2 || rows === 3 || rows === 4,
'sparseMatrixF64Range only accepts row counts of 2, 3, and 4'
);
- return kSparseMatrixF64Values[c][r];
+ return kSparseMatrixF64Values[cols][rows];
}
/**
@@ -2009,8 +2209,8 @@ export function signExtend(n: number, bits: number): number {
return (n << shift) >> shift;
}
-export interface QuantizeFunc {
- (num: number): number;
+export interface QuantizeFunc<T> {
+ (num: T): T;
}
/** @returns the closest 32-bit floating point value to the input */
@@ -2051,11 +2251,25 @@ export function quantizeToU32(num: number): number {
return Math.trunc(num);
}
+/**
+ * @returns the closest 64-bit signed integer value to the input.
+ */
+export function quantizeToI64(num: bigint): bigint {
+ if (num >= kValue.i64.positive.max) {
+ return kValue.i64.positive.max;
+ }
+ if (num <= kValue.i64.negative.min) {
+ return kValue.i64.negative.min;
+ }
+ return num;
+}
+
/** @returns whether the number is an integer and a power of two */
export function isPowerOfTwo(n: number): boolean {
if (!Number.isInteger(n)) {
return false;
}
+ assert((n | 0) === n, 'isPowerOfTwo only supports 32-bit numbers');
return n !== 0 && (n & (n - 1)) === 0;
}
@@ -2245,3 +2459,32 @@ export function every2DArray<T>(m: ROArrayArray<T>, op: (input: T) => boolean):
);
return m.every(col => col.every(el => op(el)));
}
+
+/**
+ * Subtracts 2 vectors
+ */
+export function subtractVectors(v1: readonly number[], v2: readonly number[]) {
+ return v1.map((v, i) => v - v2[i]);
+}
+
+/**
+ * Computes the dot product of 2 vectors
+ */
+export function dotProduct(v1: readonly number[], v2: readonly number[]) {
+ return v1.reduce((a, v, i) => a + v * v2[i], 0);
+}
+
+/** @returns the absolute value of a bigint */
+export function absBigInt(v: bigint): bigint {
+ return v < 0n ? -v : v;
+}
+
+/** @returns the maximum from a list of bigints */
+export function maxBigInt(...vals: bigint[]): bigint {
+ return vals.reduce((prev, cur) => (cur > prev ? cur : prev));
+}
+
+/** @returns the minimum from a list of bigints */
+export function minBigInt(...vals: bigint[]): bigint {
+ return vals.reduce((prev, cur) => (cur < prev ? cur : prev));
+}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/pretty_diff_tables.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/pretty_diff_tables.ts
index af98ab7ecf..8dab839c1f 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/pretty_diff_tables.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/pretty_diff_tables.ts
@@ -1,6 +1,28 @@
import { range } from '../../common/util/util.js';
/**
+ * @returns a function that converts numerics to strings, depending on if they
+ * should be treated as integers or not.
+ */
+export function numericToStringBuilder(is_integer: boolean): (n: number | bigint) => string {
+ if (is_integer) {
+ return (val: number | bigint): string => {
+ if (typeof val === 'number') {
+ return val.toFixed();
+ }
+ return val.toString();
+ };
+ }
+
+ return (val: number | bigint): string => {
+ if (typeof val === 'number') {
+ return val.toPrecision(6);
+ }
+ return val.toString();
+ };
+}
+
+/**
* Pretty-prints a "table" of cell values (each being `number | string`), right-aligned.
* Each row may be any iterator, including lazily-generated (potentially infinite) rows.
*
@@ -12,8 +34,11 @@ import { range } from '../../common/util/util.js';
* Each remaining argument provides one row for the table.
*/
export function generatePrettyTable(
- { fillToWidth, numberToString }: { fillToWidth: number; numberToString: (n: number) => string },
- rows: ReadonlyArray<Iterable<string | number>>
+ {
+ fillToWidth,
+ numericToString,
+ }: { fillToWidth: number; numericToString: (n: number | bigint) => string },
+ rows: ReadonlyArray<Iterable<string | number | bigint>>
): string {
const rowStrings = range(rows.length, () => '');
let totalTableWidth = 0;
@@ -23,7 +48,13 @@ export function generatePrettyTable(
for (;;) {
const cellsForColumn = iters.map(iter => {
const r = iter.next(); // Advance the iterator for each row, in lock-step.
- return r.done ? undefined : typeof r.value === 'number' ? numberToString(r.value) : r.value;
+ if (r.done) {
+ return undefined;
+ }
+ if (typeof r.value === 'number' || typeof r.value === 'bigint') {
+ return numericToString(r.value);
+ }
+ return r.value;
});
if (cellsForColumn.every(cell => cell === undefined)) break;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/shader.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/shader.ts
index 2a09061527..721d121aba 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/shader.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/shader.ts
@@ -11,6 +11,27 @@ export const kDefaultFragmentShaderCode = `
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
}`;
+// MAINTENANCE_TODO(#3344): deduplicate fullscreen quad shader code.
+export const kFullscreenQuadVertexShaderCode = `
+ struct VertexOutput {
+ @builtin(position) Position : vec4<f32>
+ };
+
+ @vertex fn main(@builtin(vertex_index) VertexIndex : u32) -> VertexOutput {
+ var pos = array<vec2<f32>, 6>(
+ vec2<f32>( 1.0, 1.0),
+ vec2<f32>( 1.0, -1.0),
+ vec2<f32>(-1.0, -1.0),
+ vec2<f32>( 1.0, 1.0),
+ vec2<f32>(-1.0, -1.0),
+ vec2<f32>(-1.0, 1.0));
+
+ var output : VertexOutput;
+ output.Position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ return output;
+ }
+`;
+
const kPlainTypeInfo = {
i32: {
suffix: '',
@@ -157,7 +178,9 @@ export function getFragmentShaderCodeWithOutput(
}`;
}
-export type TShaderStage = 'compute' | 'vertex' | 'fragment' | 'empty';
+export const kValidShaderStages = ['compute', 'vertex', 'fragment'] as const;
+export type TValidShaderStage = (typeof kValidShaderStages)[number];
+export type TShaderStage = TValidShaderStage | 'empty';
/**
* Return a foo shader of the given stage with the given entry point
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/base.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/base.ts
index 67b4fc7156..8da318aae6 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/base.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/base.ts
@@ -157,15 +157,23 @@ export function viewDimensionsForTextureDimension(textureDimension: GPUTextureDi
}
}
-/** Returns the default view dimension for a given texture descriptor. */
-export function defaultViewDimensionsForTexture(textureDescriptor: Readonly<GPUTextureDescriptor>) {
- switch (textureDescriptor.dimension) {
+/** Returns the effective view dimension for a given texture dimension and depthOrArrayLayers */
+export function effectiveViewDimensionForDimension(
+ viewDimension: GPUTextureViewDimension | undefined,
+ dimension: GPUTextureDimension | undefined,
+ depthOrArrayLayers: number
+) {
+ if (viewDimension) {
+ return viewDimension;
+ }
+
+ switch (dimension || '2d') {
case '1d':
return '1d';
- case '2d': {
- const sizeDict = reifyExtent3D(textureDescriptor.size);
- return sizeDict.depthOrArrayLayers > 1 ? '2d-array' : '2d';
- }
+ case '2d':
+ case undefined:
+ return depthOrArrayLayers > 1 ? '2d-array' : '2d';
+ break;
case '3d':
return '3d';
default:
@@ -173,6 +181,28 @@ export function defaultViewDimensionsForTexture(textureDescriptor: Readonly<GPUT
}
}
+/** Returns the effective view dimension for a given texture */
+export function effectiveViewDimensionForTexture(
+ texture: GPUTexture,
+ viewDimension: GPUTextureViewDimension | undefined
+) {
+ return effectiveViewDimensionForDimension(
+ viewDimension,
+ texture.dimension,
+ texture.depthOrArrayLayers
+ );
+}
+
+/** Returns the default view dimension for a given texture descriptor. */
+export function defaultViewDimensionsForTexture(textureDescriptor: Readonly<GPUTextureDescriptor>) {
+ const sizeDict = reifyExtent3D(textureDescriptor.size);
+ return effectiveViewDimensionForDimension(
+ undefined,
+ textureDescriptor.dimension,
+ sizeDict.depthOrArrayLayers
+ );
+}
+
/** Reifies the optional fields of `GPUTextureDescriptor`.
* MAINTENANCE_TODO: viewFormats should not be omitted here, but it seems likely that the
* @webgpu/types definition will have to change before we can include it again.
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/color_space_conversions.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/color_space_conversions.spec.ts
new file mode 100644
index 0000000000..e6b281d57a
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/color_space_conversions.spec.ts
@@ -0,0 +1,108 @@
+export const description = 'Color space conversion helpers';
+
+import { Fixture } from '../../../common/framework/fixture.js';
+import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { ErrorWithExtra } from '../../../common/util/util.js';
+import { makeInPlaceColorConversion } from '../color_space_conversion.js';
+import { clamp } from '../math.js';
+
+import { TexelView } from './texel_view.js';
+import { findFailedPixels } from './texture_ok.js';
+
+const kTestColors = [
+ [0xff, 0, 0],
+ [0, 0xff, 0],
+ [0, 0, 0xff],
+ [0x80, 0x80, 0],
+ [0, 0x80, 0x80],
+ [0x80, 0, 0x80],
+] as const;
+
+function floatToU8(v: number) {
+ return clamp(Math.round(v * 255), { min: 0, max: 255 });
+}
+
+export const g = makeTestGroup(Fixture);
+
+g.test('util_matches_2d_canvas')
+ .desc(`Test color space conversion helpers matches canvas 2d's color space conversion`)
+ .params(u =>
+ u.combineWithParams([
+ { srcColorSpace: 'srgb', dstColorSpace: 'display-p3' },
+ { srcColorSpace: 'display-p3', dstColorSpace: 'srgb' },
+ ] as { srcColorSpace: PredefinedColorSpace; dstColorSpace: PredefinedColorSpace }[])
+ )
+ .fn(t => {
+ const { srcColorSpace, dstColorSpace } = t.params;
+
+ // putImageData an ImageData(srcColorSpace) in to a canvas2D(dstColorSpace)
+ // then call getImageData. This will convert the colors via the canvas 2D API
+ const width = kTestColors.length;
+ const height = 1;
+ const imgData = new ImageData(
+ new Uint8ClampedArray(kTestColors.map(v => [...v, 255]).flat()),
+ width,
+ height,
+ { colorSpace: srcColorSpace }
+ );
+ const ctx = new OffscreenCanvas(width, height).getContext('2d', {
+ colorSpace: dstColorSpace,
+ })!;
+ ctx.putImageData(imgData, 0, 0);
+ const expectedData = ctx.getImageData(0, 0, width, height).data;
+
+ const conversionFn = makeInPlaceColorConversion({
+ srcPremultiplied: false,
+ dstPremultiplied: false,
+ srcColorSpace,
+ dstColorSpace,
+ });
+
+ // Convert the data via our conversion functions
+ const convertedData = new Uint8ClampedArray(
+ kTestColors
+ .map(color => {
+ const [R, G, B] = color.map(v => v / 255);
+ const floatColor = { R, G, B, A: 1 };
+ conversionFn(floatColor);
+ return [
+ floatToU8(floatColor.R),
+ floatToU8(floatColor.G),
+ floatToU8(floatColor.B),
+ floatToU8(floatColor.A),
+ ];
+ })
+ .flat()
+ );
+
+ const subrectOrigin = [0, 0, 0];
+ const subrectSize = [width, height, 1];
+ const areaDesc = {
+ bytesPerRow: width * 4,
+ rowsPerImage: height,
+ subrectOrigin,
+ subrectSize,
+ };
+
+ const format = 'rgba8unorm';
+ const actTexelView = TexelView.fromTextureDataByReference(format, convertedData, areaDesc);
+ const expTexelView = TexelView.fromTextureDataByReference(format, expectedData, areaDesc);
+
+ const failedPixelsMessage = findFailedPixels(
+ format,
+ { x: 0, y: 0, z: 0 },
+ { width, height, depthOrArrayLayers: 1 },
+ { actTexelView, expTexelView },
+ { maxDiffULPsForNormFormat: 0 }
+ );
+
+ if (failedPixelsMessage !== undefined) {
+ const msg = 'Color space conversion had unexpected results:\n' + failedPixelsMessage;
+ t.expectOK(
+ new ErrorWithExtra(msg, () => ({
+ expTexelView,
+ actTexelView,
+ }))
+ );
+ }
+ });
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.spec.ts
index 20f075e6f2..b063c939a9 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.spec.ts
@@ -297,7 +297,9 @@ TODO: Test NaN, Infinity, -Infinity [1]`
g.test('ufloat_texel_data_in_shader')
.desc(
`
-TODO: Test NaN, Infinity [1]`
+Note: this uses values that are representable by both rg11b10ufloat and rgb9e5ufloat.
+
+TODO: Test NaN, Infinity`
)
.params(u =>
u
@@ -312,21 +314,19 @@ TODO: Test NaN, Infinity [1]`
// Test extrema
makeParam(format, () => 0),
- // [2]: Test NaN, Infinity
-
// Test some values
- makeParam(format, () => 0.119140625),
- makeParam(format, () => 1.40625),
- makeParam(format, () => 24896),
+ makeParam(format, () => 128),
+ makeParam(format, () => 1984),
+ makeParam(format, () => 3968),
// Test scattered mixed values
makeParam(format, (bitLength, i) => {
- return [24896, 1.40625, 0.119140625, 0.23095703125][i];
+ return [128, 1984, 3968][i];
}),
// Test mixed values that are close in magnitude.
makeParam(format, (bitLength, i) => {
- return [0.1337890625, 0.17919921875, 0.119140625, 0.125][i];
+ return [0.05859375, 0.03125, 0.03515625][i];
}),
];
})
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.ts
index 42490d800b..0555ac5920 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_data.ts
@@ -1,5 +1,6 @@
import { assert, unreachable } from '../../../common/util/util.js';
import { UncompressedTextureFormat, EncodableTextureFormat } from '../../format_info.js';
+import { kValue } from '../constants.js';
import {
assertInIntegerRange,
float32ToFloatBits,
@@ -424,6 +425,8 @@ function makeNormalizedInfo(
}
const dataType: ComponentDataType = opt.signed ? 'snorm' : 'unorm';
+ const min = opt.signed ? -1 : 0;
+ const max = 1;
return {
componentOrder,
componentInfo: makePerTexelComponent(componentOrder, {
@@ -438,7 +441,7 @@ function makeNormalizedInfo(
numberToBits,
bitsToNumber,
bitsToULPFromZero,
- numericRange: { min: opt.signed ? -1 : 0, max: 1 },
+ numericRange: { min, max, finiteMin: min, finiteMax: max },
};
}
@@ -454,9 +457,9 @@ function makeIntegerInfo(
opt: { signed: boolean }
): TexelRepresentationInfo {
assert(bitLength <= 32);
- const numericRange = opt.signed
- ? { min: -(2 ** (bitLength - 1)), max: 2 ** (bitLength - 1) - 1 }
- : { min: 0, max: 2 ** bitLength - 1 };
+ const min = opt.signed ? -(2 ** (bitLength - 1)) : 0;
+ const max = opt.signed ? 2 ** (bitLength - 1) - 1 : 2 ** bitLength - 1;
+ const numericRange = { min, max, finiteMin: min, finiteMax: max };
const maxUnsignedValue = 2 ** bitLength;
const encode = applyEach(
(n: number) => (assertInIntegerRange(n, bitLength, opt.signed), n),
@@ -576,8 +579,13 @@ function makeFloatInfo(
bitsToNumber,
bitsToULPFromZero,
numericRange: restrictedDepth
- ? { min: 0, max: 1 }
- : { min: Number.NEGATIVE_INFINITY, max: Number.POSITIVE_INFINITY },
+ ? { min: 0, max: 1, finiteMin: 0, finiteMax: 1 }
+ : {
+ min: Number.NEGATIVE_INFINITY,
+ max: Number.POSITIVE_INFINITY,
+ finiteMin: bitLength === 32 ? kValue.f32.negative.min : kValue.f16.negative.min,
+ finiteMax: bitLength === 32 ? kValue.f32.positive.max : kValue.f16.positive.max,
+ },
};
}
@@ -592,6 +600,7 @@ const identity = (n: number) => n;
const kFloat11Format = { signed: 0, exponentBits: 5, mantissaBits: 6, bias: 15 } as const;
const kFloat10Format = { signed: 0, exponentBits: 5, mantissaBits: 5, bias: 15 } as const;
+export type PerComponentFiniteMax = Record<TexelComponent, number>;
export type TexelRepresentationInfo = {
/** Order of components in the packed representation. */
readonly componentOrder: TexelComponent[];
@@ -619,7 +628,12 @@ export type TexelRepresentationInfo = {
/** Convert integer bit representations into ULPs-from-zero, e.g. unorm8 255 -> 255 ULPs */
readonly bitsToULPFromZero: ComponentMapFn;
/** The valid range of numeric "color" values, e.g. [0, Infinity] for ufloat. */
- readonly numericRange: null | { min: number; max: number };
+ readonly numericRange: null | {
+ min: number;
+ max: number;
+ finiteMin: number;
+ finiteMax: number | PerComponentFiniteMax;
+ };
// Add fields as needed
};
@@ -765,7 +779,7 @@ export const kTexelRepresentationInfo: {
A: normalizedIntegerAsFloat(components.A!, 2, false),
}),
bitsToULPFromZero: components => components,
- numericRange: { min: 0, max: 1 },
+ numericRange: { min: 0, max: 1, finiteMin: 0, finiteMax: 1 },
},
rg11b10ufloat: {
componentOrder: kRGB,
@@ -809,7 +823,16 @@ export const kTexelRepresentationInfo: {
G: floatBitsToNormalULPFromZero(components.G!, kFloat11Format),
B: floatBitsToNormalULPFromZero(components.B!, kFloat10Format),
}),
- numericRange: { min: 0, max: Number.POSITIVE_INFINITY },
+ numericRange: {
+ min: 0,
+ max: Number.POSITIVE_INFINITY,
+ finiteMin: 0,
+ finiteMax: {
+ R: floatBitsToNumber(0b111_1011_1111, kFloat11Format),
+ G: floatBitsToNumber(0b111_1011_1111, kFloat11Format),
+ B: floatBitsToNumber(0b11_1101_1111, kFloat10Format),
+ } as PerComponentFiniteMax,
+ },
},
rgb9e5ufloat: {
componentOrder: kRGB,
@@ -854,7 +877,12 @@ export const kTexelRepresentationInfo: {
G: floatBitsToNormalULPFromZero(components.G!, kUFloat9e5Format),
B: floatBitsToNormalULPFromZero(components.B!, kUFloat9e5Format),
}),
- numericRange: { min: 0, max: Number.POSITIVE_INFINITY },
+ numericRange: {
+ min: 0,
+ max: Number.POSITIVE_INFINITY,
+ finiteMin: 0,
+ finiteMax: ufloatM9E5BitsToNumber(0b11_1111_1111_1111, kUFloat9e5Format),
+ },
},
depth32float: makeFloatInfo([TexelComponent.Depth], 32, { restrictedDepth: true }),
depth16unorm: makeNormalizedInfo([TexelComponent.Depth], 16, { signed: false, sRGB: false }),
@@ -868,7 +896,7 @@ export const kTexelRepresentationInfo: {
numberToBits: () => unreachable('depth24plus has no representation'),
bitsToNumber: () => unreachable('depth24plus has no representation'),
bitsToULPFromZero: () => unreachable('depth24plus has no representation'),
- numericRange: { min: 0, max: 1 },
+ numericRange: { min: 0, max: 1, finiteMin: 0, finiteMax: 1 },
},
stencil8: makeIntegerInfo([TexelComponent.Stencil], 8, { signed: false }),
'depth32float-stencil8': {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_view.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_view.ts
index fea23b674e..0b920ef699 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_view.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_view.ts
@@ -1,6 +1,6 @@
import { assert, memcpy } from '../../../common/util/util.js';
import { kTextureFormatInfo, EncodableTextureFormat } from '../../format_info.js';
-import { generatePrettyTable } from '../pretty_diff_tables.js';
+import { generatePrettyTable, numericToStringBuilder } from '../pretty_diff_tables.js';
import { reifyExtent3D, reifyOrigin3D } from '../unions.js';
import { fullSubrectCoordinates } from './base.js';
@@ -166,10 +166,13 @@ export class TexelView {
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);
+ // MAINTENANCE_TODO: Print depth-stencil formats as float+int instead of float+float.
+ const printAsInteger = info.color
+ ? // For color, pick the type based on the format type
+ ['uint', 'sint'].includes(info.color.type)
+ : // Print depth as "float", depth-stencil as "float,float", stencil as "int".
+ !info.depth;
+ const numericToString = numericToStringBuilder(printAsInteger);
const componentOrderStr = repr.componentOrder.join(',') + ':';
const subrectCoords = [...fullSubrectCoordinates(subrectOrigin, subrectSize)];
@@ -188,13 +191,13 @@ export class TexelView {
yield* [' act. colors', '==', componentOrderStr];
for (const coords of subrectCoords) {
const pixel = t.color(coords);
- yield `${repr.componentOrder.map(ch => numberToString(pixel[ch]!)).join(',')}`;
+ yield `${repr.componentOrder.map(ch => numericToString(pixel[ch]!)).join(',')}`;
}
})(this);
const opts = {
fillToWidth: 120,
- numberToString,
+ numericToString,
};
return `${generatePrettyTable(opts, [printCoords, printActualBytes, printActualColors])}`;
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texture_ok.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texture_ok.ts
index 7b85489246..d0267f0627 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texture_ok.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texture_ok.ts
@@ -2,7 +2,7 @@ import { assert, ErrorWithExtra, unreachable } from '../../../common/util/util.j
import { kTextureFormatInfo, EncodableTextureFormat } from '../../format_info.js';
import { GPUTest } from '../../gpu_test.js';
import { numbersApproximatelyEqual } from '../conversion.js';
-import { generatePrettyTable } from '../pretty_diff_tables.js';
+import { generatePrettyTable, numericToStringBuilder } from '../pretty_diff_tables.js';
import { reifyExtent3D, reifyOrigin3D } from '../unions.js';
import { fullSubrectCoordinates } from './base.js';
@@ -223,11 +223,13 @@ export function findFailedPixels(
const info = kTextureFormatInfo[format];
const repr = kTexelRepresentationInfo[format];
-
- const integerSampleType = info.sampleType === 'uint' || info.sampleType === 'sint';
- const numberToString = integerSampleType
- ? (n: number) => n.toFixed()
- : (n: number) => n.toPrecision(6);
+ // MAINTENANCE_TODO: Print depth-stencil formats as float+int instead of float+float.
+ const printAsInteger = info.color
+ ? // For color, pick the type based on the format type
+ ['uint', 'sint'].includes(info.color.type)
+ : // Print depth as "float", depth-stencil as "float,float", stencil as "int".
+ !info.depth;
+ const numericToString = numericToStringBuilder(printAsInteger);
const componentOrderStr = repr.componentOrder.join(',') + ':';
@@ -245,14 +247,14 @@ export function findFailedPixels(
yield* [' act. colors', '==', componentOrderStr];
for (const coords of failedPixels) {
const pixel = actTexelView.color(coords);
- yield `${repr.componentOrder.map(ch => numberToString(pixel[ch]!)).join(',')}`;
+ yield `${repr.componentOrder.map(ch => numericToString(pixel[ch]!)).join(',')}`;
}
})();
const printExpectedColors = (function* () {
yield* [' exp. colors', '==', componentOrderStr];
for (const coords of failedPixels) {
const pixel = expTexelView.color(coords);
- yield `${repr.componentOrder.map(ch => numberToString(pixel[ch]!)).join(',')}`;
+ yield `${repr.componentOrder.map(ch => numericToString(pixel[ch]!)).join(',')}`;
}
})();
const printActualULPs = (function* () {
@@ -272,7 +274,7 @@ export function findFailedPixels(
const opts = {
fillToWidth: 120,
- numberToString,
+ numericToString,
};
return `\
between ${lowerCorner} and ${upperCorner} inclusive:
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/configure.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/configure.spec.ts
index 163930e20e..b4da68ba36 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/configure.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/configure.spec.ts
@@ -13,7 +13,6 @@ import { GPUConst } from '../../constants.js';
import {
kAllTextureFormats,
kFeaturesForFormats,
- kTextureFormats,
filterFormatsByFeature,
viewCompatible,
} from '../../format_info.js';
@@ -387,7 +386,7 @@ g.test('viewFormats')
.combine('viewFormatFeature', kFeaturesForFormats)
.beginSubcases()
.expand('viewFormat', ({ viewFormatFeature }) =>
- filterFormatsByFeature(viewFormatFeature, kTextureFormats)
+ filterFormatsByFeature(viewFormatFeature, kAllTextureFormats)
)
)
.beforeAllSubcases(t => {
@@ -402,7 +401,7 @@ g.test('viewFormats')
const ctx = canvas.getContext('webgpu');
assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
- const compatible = viewCompatible(format, viewFormat);
+ const compatible = viewCompatible(t.isCompatibility, format, viewFormat);
// Test configure() produces an error if the formats aren't compatible.
t.expectValidationError(() => {
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/getCurrentTexture.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/getCurrentTexture.spec.ts
index 609dacb907..633d88d2b8 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/getCurrentTexture.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/getCurrentTexture.spec.ts
@@ -40,6 +40,27 @@ class GPUContextTest extends GPUTest {
return ctx;
}
+
+ expectTextureDestroyed(texture: GPUTexture, expectDestroyed = true) {
+ this.expectValidationError(() => {
+ // Try using the texture in a render pass. Because it's a canvas texture
+ // it should have RENDER_ATTACHMENT usage.
+ assert((texture.usage & GPUTextureUsage.RENDER_ATTACHMENT) !== 0);
+ const encoder = this.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: texture.createView(),
+ loadOp: 'clear',
+ storeOp: 'store',
+ },
+ ],
+ });
+ pass.end();
+ // Submitting should generate a validation error if the texture is destroyed.
+ this.queue.submit([encoder.finish()]);
+ }, expectDestroyed);
+ }
}
export const g = makeTestGroup(GPUContextTest);
@@ -170,7 +191,7 @@ g.test('multiple_frames')
// Ensure that each frame a new texture object is returned.
t.expect(currentTexture !== prevTexture);
- // Ensure that texture contents are transparent black.
+ // Ensure that the texture's initial contents are transparent black.
t.expectSingleColor(currentTexture, currentTexture.format, {
size: [currentTexture.width, currentTexture.height, 1],
exp: { R: 0, G: 0, B: 0, A: 0 },
@@ -178,7 +199,8 @@ g.test('multiple_frames')
}
if (clearTexture) {
- // Clear the texture to test that texture contents don't carry over from frame to frame.
+ // Fill the texture with a non-zero color, to test that texture
+ // contents don't carry over from frame to frame.
const encoder = t.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
@@ -215,10 +237,8 @@ g.test('multiple_frames')
}
}
- // Call frameCheck for the first time from requestAnimationFrame
- // To make sure two frameChecks are run in different frames for onscreen canvas.
- // offscreen canvas doesn't care.
- requestAnimationFrame(frameCheck);
+ // Render the first frame immediately. The rest will be triggered recursively.
+ frameCheck();
});
});
@@ -235,6 +255,8 @@ g.test('resize')
// Trigger a resize by changing the width.
ctx.canvas.width = 4;
+ t.expectTextureDestroyed(prevTexture);
+
// When the canvas resizes the texture returned by getCurrentTexture should immediately begin
// returning a new texture matching the update dimensions.
let currentTexture = ctx.getCurrentTexture();
@@ -263,7 +285,6 @@ g.test('resize')
t.expect(currentTexture.height === ctx.canvas.height);
t.expect(prevTexture.width === 4);
t.expect(prevTexture.height === 2);
- prevTexture = currentTexture;
// Ensure that texture contents are transparent black.
t.expectSingleColor(currentTexture, currentTexture.format, {
@@ -271,13 +292,31 @@ g.test('resize')
exp: { R: 0, G: 0, B: 0, A: 0 },
});
- // Simply setting the canvas width and height values to their current values should not trigger
- // a change in the texture.
- ctx.canvas.width = 4;
- ctx.canvas.height = 4;
-
- currentTexture = ctx.getCurrentTexture();
- t.expect(prevTexture === currentTexture);
+ // HTMLCanvasElement behaves differently than OffscreenCanvas
+ if (t.params.canvasType === 'onscreen') {
+ // Ensure canvas goes back to defaults when set to negative numbers.
+ ctx.canvas.width = -1;
+ currentTexture = ctx.getCurrentTexture();
+ t.expect(currentTexture.width === 300);
+ t.expect(currentTexture.height === 4);
+
+ ctx.canvas.height = -1;
+ currentTexture = ctx.getCurrentTexture();
+ t.expect(currentTexture.width === 300);
+ t.expect(currentTexture.height === 150);
+
+ // Setting the canvas width and height values to their current values should
+ // still trigger a change in the texture.
+ prevTexture = ctx.getCurrentTexture();
+ const { width, height } = ctx.canvas;
+ ctx.canvas.width = width;
+ ctx.canvas.height = height;
+
+ t.expectTextureDestroyed(prevTexture);
+
+ currentTexture = ctx.getCurrentTexture();
+ t.expect(prevTexture !== currentTexture);
+ }
});
g.test('expiry')
@@ -307,6 +346,14 @@ TODO: test more canvas types, and ways to update the rendering
.combine('prevFrameCallsite', ['runInNewCanvasFrame', 'requestAnimationFrame'] as const)
.combine('getCurrentTextureAgain', [true, false] as const)
)
+ .beforeAllSubcases(t => {
+ if (
+ t.params.prevFrameCallsite === 'requestAnimationFrame' &&
+ typeof requestAnimationFrame === 'undefined'
+ ) {
+ throw new SkipTestCase('requestAnimationFrame not available');
+ }
+ })
.fn(t => {
const { canvasType, prevFrameCallsite, getCurrentTextureAgain } = t.params;
const ctx = t.initCanvasContext(t.params.canvasType);
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.ts
index 7fd7142f00..84d940ed04 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/canvas/readbackFromWebGPUCanvas.spec.ts
@@ -14,7 +14,12 @@ TODO: implement all canvas types, see TODO on kCanvasTypes.
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
-import { assert, raceWithRejectOnTimeout, unreachable } from '../../../common/util/util.js';
+import {
+ ErrorWithExtra,
+ assert,
+ raceWithRejectOnTimeout,
+ unreachable,
+} from '../../../common/util/util.js';
import {
kCanvasAlphaModes,
kCanvasColorSpaces,
@@ -27,7 +32,10 @@ import {
CanvasType,
createCanvas,
createOnscreenCanvas,
+ createOffscreenCanvas,
} from '../../util/create_elements.js';
+import { TexelView } from '../../util/texture/texel_view.js';
+import { findFailedPixels } from '../../util/texture/texture_ok.js';
export const g = makeTestGroup(GPUTest);
@@ -61,6 +69,26 @@ const expect = {
]),
};
+/**
+ * Given 4 pixels in rgba8unorm format, puts them into an ImageData
+ * of the specified color space and then puts them into an srgb color space
+ * canvas (the default). If the color space is different there will be a
+ * conversion. Returns the resulting 4 pixels in rgba8unorm format.
+ */
+function convertRGBA8UnormBytesToColorSpace(
+ expected: Uint8ClampedArray,
+ srcColorSpace: PredefinedColorSpace,
+ dstColorSpace: PredefinedColorSpace
+) {
+ const srcImgData = new ImageData(2, 2, { colorSpace: srcColorSpace });
+ srcImgData.data.set(expected);
+ const dstCanvas = new OffscreenCanvas(2, 2);
+ const dstCtx = dstCanvas.getContext('2d', { colorSpace: dstColorSpace });
+ assert(dstCtx !== null);
+ dstCtx.putImageData(srcImgData, 0, 0);
+ return dstCtx.getImageData(0, 0, 2, 2).data;
+}
+
function initWebGPUCanvasContent<T extends CanvasType>(
t: GPUTest,
format: GPUTextureFormat,
@@ -119,7 +147,7 @@ function drawImageSourceIntoCanvas(
image: CanvasImageSource,
colorSpace: PredefinedColorSpace
) {
- const canvas: HTMLCanvasElement = createOnscreenCanvas(t, 2, 2);
+ const canvas = createOffscreenCanvas(t, 2, 2);
const ctx = canvas.getContext('2d', { colorSpace });
assert(ctx !== null);
ctx.drawImage(image, 0, 0);
@@ -147,22 +175,13 @@ function checkImageResultWithDifferentColorSpaceCanvas(
// draw the WebGPU derived data into a canvas
const fromWebGPUCtx = drawImageSourceIntoCanvas(t, image, destinationColorSpace);
- // create a 2D canvas with the same source data in the same color space as the WebGPU
- // canvas
- const source2DCanvas: HTMLCanvasElement = createOnscreenCanvas(t, 2, 2);
- const source2DCtx = source2DCanvas.getContext('2d', { colorSpace: sourceColorSpace });
- assert(source2DCtx !== null);
- const imgData = source2DCtx.getImageData(0, 0, 2, 2);
- imgData.data.set(sourceData);
- source2DCtx.putImageData(imgData, 0, 0);
-
- // draw the source 2D canvas into another 2D canvas with the destination color space and
- // then pull out the data. This result should be the same as the WebGPU derived data
- // written to a 2D canvas of the same destination color space.
- const from2DCtx = drawImageSourceIntoCanvas(t, source2DCanvas, destinationColorSpace);
- const expect = from2DCtx.getImageData(0, 0, 2, 2).data;
-
- readPixelsFrom2DCanvasAndCompare(t, fromWebGPUCtx, expect);
+ const expect = convertRGBA8UnormBytesToColorSpace(
+ sourceData,
+ sourceColorSpace,
+ destinationColorSpace
+ );
+
+ readPixelsFrom2DCanvasAndCompare(t, fromWebGPUCtx, expect, 2);
}
function checkImageResult(
@@ -171,18 +190,55 @@ function checkImageResult(
sourceColorSpace: PredefinedColorSpace,
expect: Uint8ClampedArray
) {
+ // canvas(colorSpace)->img(colorSpace)->canvas(colorSpace).drawImage->canvas(colorSpace).getImageData->actual
+ // hard coded data->expected
checkImageResultWithSameColorSpaceCanvas(t, image, sourceColorSpace, expect);
+
+ // canvas(colorSpace)->img(colorSpace)->canvas(diffColorSpace).drawImage->canvas(diffColorSpace).getImageData->actual
+ // hard coded data->ImageData(colorSpace)->canvas(diffColorSpace).putImageData->canvas(diffColorSpace).getImageData->expected
checkImageResultWithDifferentColorSpaceCanvas(t, image, sourceColorSpace, expect);
}
function readPixelsFrom2DCanvasAndCompare(
t: GPUTest,
ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
- expect: Uint8ClampedArray
+ expect: Uint8ClampedArray,
+ maxDiffULPsForNormFormat = 0
) {
- const actual = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height).data;
+ const { width, height } = ctx.canvas;
+ const actual = ctx.getImageData(0, 0, width, height).data;
- t.expectOK(checkElementsEqual(actual, expect));
+ const subrectOrigin = [0, 0, 0];
+ const subrectSize = [width, height, 1];
+
+ const areaDesc = {
+ bytesPerRow: width * 4,
+ rowsPerImage: height,
+ subrectOrigin,
+ subrectSize,
+ };
+
+ const format = 'rgba8unorm';
+ const actTexelView = TexelView.fromTextureDataByReference(format, actual, areaDesc);
+ const expTexelView = TexelView.fromTextureDataByReference(format, expect, areaDesc);
+
+ const failedPixelsMessage = findFailedPixels(
+ format,
+ { x: 0, y: 0, z: 0 },
+ { width, height, depthOrArrayLayers: 1 },
+ { actTexelView, expTexelView },
+ { maxDiffULPsForNormFormat }
+ );
+
+ if (failedPixelsMessage !== undefined) {
+ const msg = 'Canvas had unexpected contents:\n' + failedPixelsMessage;
+ t.expectOK(
+ new ErrorWithExtra(msg, () => ({
+ expTexelView,
+ actTexelView,
+ }))
+ );
+ }
}
g.test('onscreenCanvas,snapshot')
@@ -265,7 +321,7 @@ g.test('offscreenCanvas,snapshot')
.combine('format', kCanvasTextureFormats)
.combine('alphaMode', kCanvasAlphaModes)
.combine('colorSpace', kCanvasColorSpaces)
- .combine('snapshotType', ['convertToBlob', 'transferToImageBitmap', 'imageBitmap'])
+ .combine('snapshotType', ['convertToBlob', 'transferToImageBitmap', 'imageBitmap'] as const)
)
.fn(async t => {
const offscreenCanvas = initWebGPUCanvasContent(
@@ -284,11 +340,7 @@ g.test('offscreenCanvas,snapshot')
return;
}
const blob = await offscreenCanvas.convertToBlob();
- const url = URL.createObjectURL(blob);
- const img = new Image(offscreenCanvas.width, offscreenCanvas.height);
- img.src = url;
- await raceWithRejectOnTimeout(img.decode(), 5000, 'load image timeout');
- snapshot = img;
+ snapshot = await createImageBitmap(blob);
break;
}
case 'transferToImageBitmap': {
@@ -383,6 +435,19 @@ g.test('drawTo2DCanvas')
- colorSpace = {"srgb", "display-p3"}
- WebGPU canvas type = {"onscreen", "offscreen"}
- 2d canvas type = {"onscreen", "offscreen"}
+
+
+ * makes a webgpu canvas with the given colorSpace and puts data in via copy convoluted
+ copy process
+ * makes a 2d canvas with 'srgb' colorSpace (the default)
+ * draws the webgpu canvas into the 2d canvas so if the color spaces do not match
+ there will be a conversion.
+ * gets the pixels from the 2d canvas via getImageData
+ * compares them to hard coded values that are converted to expected values by copying
+ to an ImageData of the given color space, and then using putImageData into an srgb canvas.
+
+ canvas(colorSpace) -> canvas(srgb).drawImage -> canvas(srgb).getImageData -> actual
+ ImageData(colorSpace) -> canvas(srgb).putImageData -> canvas(srgb).getImageData -> expected
`
)
.params(u =>
@@ -396,35 +461,47 @@ g.test('drawTo2DCanvas')
.fn(t => {
const { format, webgpuCanvasType, alphaMode, colorSpace, canvas2DType } = t.params;
- const canvas = initWebGPUCanvasContent(t, format, alphaMode, colorSpace, webgpuCanvasType);
+ const webgpuCanvas = initWebGPUCanvasContent(
+ t,
+ format,
+ alphaMode,
+ colorSpace,
+ webgpuCanvasType
+ );
- const expectCanvas = createCanvas(t, canvas2DType, canvas.width, canvas.height);
- const ctx = expectCanvas.getContext('2d') as CanvasRenderingContext2D;
+ const actualCanvas = createCanvas(t, canvas2DType, webgpuCanvas.width, webgpuCanvas.height);
+ const ctx = actualCanvas.getContext('2d') as CanvasRenderingContext2D;
if (ctx === null) {
t.skip(canvas2DType + ' canvas cannot get 2d context');
return;
}
- ctx.drawImage(canvas, 0, 0);
- readPixelsFrom2DCanvasAndCompare(t, ctx, expect[t.params.alphaMode]);
+ ctx.drawImage(webgpuCanvas, 0, 0);
+
+ readPixelsFrom2DCanvasAndCompare(
+ t,
+ ctx,
+ convertRGBA8UnormBytesToColorSpace(expect[t.params.alphaMode], colorSpace, 'srgb')
+ );
});
g.test('transferToImageBitmap_unconfigured_nonzero_size')
.desc(
`Regression test for a crash when calling transferImageBitmap on an unconfigured. Case where the canvas is not empty`
)
+ .params(u => u.combine('readbackCanvasType', ['onscreen', 'offscreen'] as const))
.fn(t => {
- const canvas = createCanvas(t, 'offscreen', 2, 3);
+ const kWidth = 2;
+ const kHeight = 3;
+ const canvas = createCanvas(t, 'offscreen', kWidth, kHeight);
canvas.getContext('webgpu');
// Transferring gives an ImageBitmap of the correct size filled with transparent black.
const ib = canvas.transferToImageBitmap();
- t.expect(ib.width === canvas.width);
- t.expect(ib.height === canvas.height);
+ t.expect(ib.width === kWidth);
+ t.expect(ib.height === kHeight);
- const readbackCanvas = document.createElement('canvas');
- readbackCanvas.width = canvas.width;
- readbackCanvas.height = canvas.height;
+ const readbackCanvas = createCanvas(t, t.params.readbackCanvasType, kWidth, kHeight);
const readbackContext = readbackCanvas.getContext('2d', {
alpha: true,
});
@@ -434,7 +511,7 @@ g.test('transferToImageBitmap_unconfigured_nonzero_size')
}
// Since there isn't a configuration we expect the ImageBitmap to have the default alphaMode of "opaque".
- const expected = new Uint8ClampedArray(canvas.width * canvas.height * 4);
+ const expected = new Uint8ClampedArray(kWidth * kHeight * 4);
for (let i = 0; i < expected.byteLength; i += 4) {
expected[i + 0] = 0;
expected[i + 1] = 0;
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/canvas.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/canvas.spec.ts
index 06c3cd30b2..dc80eff9de 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/canvas.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/canvas.spec.ts
@@ -97,9 +97,7 @@ class F extends CopyToTextureUtils {
}
const imageData = new ImageData(imagePixels, width, height, { colorSpace });
- // MAINTENANCE_TODO: Remove as any when tsc support imageData.colorSpace
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- if (typeof (imageData as any).colorSpace === 'undefined') {
+ if (typeof imageData.colorSpace === 'undefined') {
this.skip('color space attr is not supported for ImageData');
}
@@ -762,7 +760,7 @@ g.test('color_space_conversion')
.params(u =>
u
.combine('srcColorSpace', ['srgb', 'display-p3'] as const)
- .combine('dstColorSpace', ['srgb'] as const)
+ .combine('dstColorSpace', ['srgb', 'display-p3'] as const)
.combine('dstColorFormat', kValidTextureFormatsForCopyE2T)
.combine('dstPremultiplied', [true, false])
.combine('srcDoFlipYDuringCopy', [true, false])
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/video.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/video.spec.ts
index 1888eb7e58..3f73a41c84 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/video.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/video.spec.ts
@@ -1,8 +1,10 @@
export const description = `
copyToTexture with HTMLVideoElement and VideoFrame.
-- videos with various encodings/formats (webm vp8, webm vp9, ogg theora, mp4), color spaces
- (bt.601, bt.709, bt.2020)
+- videos with various encodings/formats (webm vp8, webm vp9, ogg theora, mp4), video color spaces
+ (bt.601, bt.709, bt.2020) and dst color spaces(display-p3, srgb).
+
+ TODO: Test video in BT.2020 color space
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
@@ -11,7 +13,11 @@ import {
startPlayingAndWaitForVideo,
getVideoElement,
getVideoFrameFromVideoElement,
- kVideoExpectations,
+ convertToUnorm8,
+ kPredefinedColorSpace,
+ kVideoNames,
+ kVideoInfo,
+ kVideoExpectedColors,
} from '../../web_platform/util.js';
const kFormat = 'rgba8unorm';
@@ -36,17 +42,17 @@ It creates HTMLVideoElement with videos under Resource folder.
- Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcDoFlipYDuringCopy' in cases)
- TODO: partial copy tests should be added
- TODO: all valid dstColorFormat tests should be added.
- - TODO: dst color space tests need to be added
`
)
.params(u =>
u //
- .combineWithParams(kVideoExpectations)
+ .combine('videoName', kVideoNames)
.combine('sourceType', ['VideoElement', 'VideoFrame'] as const)
.combine('srcDoFlipYDuringCopy', [true, false])
+ .combine('dstColorSpace', kPredefinedColorSpace)
)
.fn(async t => {
- const { videoName, sourceType, srcDoFlipYDuringCopy } = t.params;
+ const { videoName, sourceType, srcDoFlipYDuringCopy, dstColorSpace } = t.params;
if (sourceType === 'VideoFrame' && typeof VideoFrame === 'undefined') {
t.skip('WebCodec is not supported');
@@ -58,8 +64,8 @@ It creates HTMLVideoElement with videos under Resource folder.
let source, width, height;
if (sourceType === 'VideoFrame') {
source = await getVideoFrameFromVideoElement(t, videoElement);
- width = source.codedWidth;
- height = source.codedHeight;
+ width = source.displayWidth;
+ height = source.displayHeight;
} else {
source = videoElement;
width = source.videoWidth;
@@ -82,33 +88,63 @@ It creates HTMLVideoElement with videos under Resource folder.
{
texture: dstTexture,
origin: { x: 0, y: 0 },
- colorSpace: 'srgb',
+ colorSpace: dstColorSpace,
premultipliedAlpha: true,
},
{ width, height, depthOrArrayLayers: 1 }
);
+ const srcColorSpace = kVideoInfo[videoName].colorSpace;
+ const presentColors = kVideoExpectedColors[srcColorSpace][dstColorSpace];
+
+ // visible rect is whole frame, no clipping.
+ const expect = kVideoInfo[videoName].display;
+
if (srcDoFlipYDuringCopy) {
t.expectSinglePixelComparisonsAreOkInTexture({ texture: dstTexture }, [
- // Top-left should be blue.
- { coord: { x: width * 0.25, y: height * 0.25 }, exp: t.params._blueExpectation },
- // Top-right should be green.
- { coord: { x: width * 0.75, y: height * 0.25 }, exp: t.params._greenExpectation },
- // Bottom-left should be yellow.
- { coord: { x: width * 0.25, y: height * 0.75 }, exp: t.params._yellowExpectation },
- // Bottom-right should be red.
- { coord: { x: width * 0.75, y: height * 0.75 }, exp: t.params._redExpectation },
+ // Flipped top-left.
+ {
+ coord: { x: width * 0.25, y: height * 0.25 },
+ exp: convertToUnorm8(presentColors[expect.bottomLeftColor]),
+ },
+ // Flipped top-right.
+ {
+ coord: { x: width * 0.75, y: height * 0.25 },
+ exp: convertToUnorm8(presentColors[expect.bottomRightColor]),
+ },
+ // Flipped bottom-left.
+ {
+ coord: { x: width * 0.25, y: height * 0.75 },
+ exp: convertToUnorm8(presentColors[expect.topLeftColor]),
+ },
+ // Flipped bottom-right.
+ {
+ coord: { x: width * 0.75, y: height * 0.75 },
+ exp: convertToUnorm8(presentColors[expect.topRightColor]),
+ },
]);
} else {
t.expectSinglePixelComparisonsAreOkInTexture({ texture: dstTexture }, [
- // Top-left should be yellow.
- { coord: { x: width * 0.25, y: height * 0.25 }, exp: t.params._yellowExpectation },
- // Top-right should be red.
- { coord: { x: width * 0.75, y: height * 0.25 }, exp: t.params._redExpectation },
- // Bottom-left should be blue.
- { coord: { x: width * 0.25, y: height * 0.75 }, exp: t.params._blueExpectation },
- // Bottom-right should be green.
- { coord: { x: width * 0.75, y: height * 0.75 }, exp: t.params._greenExpectation },
+ // Top-left.
+ {
+ coord: { x: width * 0.25, y: height * 0.25 },
+ exp: convertToUnorm8(presentColors[expect.topLeftColor]),
+ },
+ // Top-right.
+ {
+ coord: { x: width * 0.75, y: height * 0.25 },
+ exp: convertToUnorm8(presentColors[expect.topRightColor]),
+ },
+ // Bottom-left.
+ {
+ coord: { x: width * 0.25, y: height * 0.75 },
+ exp: convertToUnorm8(presentColors[expect.bottomLeftColor]),
+ },
+ // Bottom-right.
+ {
+ coord: { x: width * 0.75, y: height * 0.75 },
+ exp: convertToUnorm8(presentColors[expect.bottomRightColor]),
+ },
]);
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/external_texture/video.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/external_texture/video.spec.ts
index baa2a985d2..8e812ccd2a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/external_texture/video.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/external_texture/video.spec.ts
@@ -1,20 +1,25 @@
export const description = `
Tests for external textures from HTMLVideoElement (and other video-type sources?).
-- videos with various encodings/formats (webm vp8, webm vp9, ogg theora, mp4), color spaces
- (bt.601, bt.709, bt.2020)
+- videos with various encodings/formats (webm vp8, webm vp9, ogg theora, mp4), video color spaces
+ (bt.601, bt.709, bt.2020) and dst color spaces(display-p3, srgb)
TODO: consider whether external_texture and copyToTexture video tests should be in the same file
+TODO(#3193): Test video in BT.2020 color space
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { GPUTest, TextureTestMixin } from '../../gpu_test.js';
+import { createCanvas } from '../../util/create_elements.js';
import {
startPlayingAndWaitForVideo,
getVideoFrameFromVideoElement,
getVideoElement,
- kVideoExpectations,
- kVideoRotationExpectations,
+ convertToUnorm8,
+ kPredefinedColorSpace,
+ kVideoNames,
+ kVideoInfo,
+ kVideoExpectedColors,
} from '../../web_platform/util.js';
const kHeight = 16;
@@ -23,7 +28,10 @@ const kFormat = 'rgba8unorm';
export const g = makeTestGroup(TextureTestMixin(GPUTest));
-function createExternalTextureSamplingTestPipeline(t: GPUTest): GPURenderPipeline {
+function createExternalTextureSamplingTestPipeline(
+ t: GPUTest,
+ colorAttachmentFormat: GPUTextureFormat = kFormat
+): GPURenderPipeline {
const pipeline = t.device.createRenderPipeline({
layout: 'auto',
vertex: {
@@ -59,7 +67,7 @@ function createExternalTextureSamplingTestPipeline(t: GPUTest): GPURenderPipelin
entryPoint: 'main',
targets: [
{
- format: kFormat,
+ format: colorAttachmentFormat,
},
],
},
@@ -73,13 +81,14 @@ function createExternalTextureSamplingTestBindGroup(
t: GPUTest,
checkNonStandardIsZeroCopy: true | undefined,
source: HTMLVideoElement | VideoFrame,
- pipeline: GPURenderPipeline
+ pipeline: GPURenderPipeline,
+ dstColorSpace: PredefinedColorSpace
): GPUBindGroup {
const linearSampler = t.device.createSampler();
const externalTexture = t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
+ source,
+ colorSpace: dstColorSpace,
});
if (checkNonStandardIsZeroCopy) {
@@ -133,22 +142,24 @@ g.test('importExternalTexture,sample')
.desc(
`
Tests that we can import an HTMLVideoElement/VideoFrame into a GPUExternalTexture, sample from it
-for several combinations of video format and color space.
+for several combinations of video format, video color spaces and dst color spaces.
`
)
.params(u =>
u //
.combineWithParams(checkNonStandardIsZeroCopyIfAvailable())
+ .combine('videoName', kVideoNames)
.combine('sourceType', ['VideoElement', 'VideoFrame'] as const)
- .combineWithParams(kVideoExpectations)
+ .combine('dstColorSpace', kPredefinedColorSpace)
)
.fn(async t => {
- const sourceType = t.params.sourceType;
+ const { videoName, sourceType, dstColorSpace } = t.params;
+
if (sourceType === 'VideoFrame' && typeof VideoFrame === 'undefined') {
t.skip('WebCodec is not supported');
}
- const videoElement = getVideoElement(t, t.params.videoName);
+ const videoElement = getVideoElement(t, videoName);
await startPlayingAndWaitForVideo(videoElement, async () => {
const source =
@@ -167,7 +178,8 @@ for several combinations of video format and color space.
t,
t.params.checkNonStandardIsZeroCopy,
source,
- pipeline
+ pipeline,
+ dstColorSpace
);
const commandEncoder = t.device.createCommandEncoder();
@@ -187,88 +199,162 @@ for several combinations of video format and color space.
passEncoder.end();
t.device.queue.submit([commandEncoder.finish()]);
+ const srcColorSpace = kVideoInfo[videoName].colorSpace;
+ const presentColors = kVideoExpectedColors[srcColorSpace][dstColorSpace];
+
+ // visible rect is whole frame, no clipping.
+ const expect = kVideoInfo[videoName].display;
+
// For validation, we sample a few pixels away from the edges to avoid compression
// artifacts.
t.expectSinglePixelComparisonsAreOkInTexture({ texture: colorAttachment }, [
- // Top-left should be yellow.
- { coord: { x: kWidth * 0.25, y: kHeight * 0.25 }, exp: t.params._yellowExpectation },
- // Top-right should be red.
- { coord: { x: kWidth * 0.75, y: kHeight * 0.25 }, exp: t.params._redExpectation },
- // Bottom-left should be blue.
- { coord: { x: kWidth * 0.25, y: kHeight * 0.75 }, exp: t.params._blueExpectation },
- // Bottom-right should be green.
- { coord: { x: kWidth * 0.75, y: kHeight * 0.75 }, exp: t.params._greenExpectation },
+ // Top-left.
+ {
+ coord: { x: kWidth * 0.25, y: kHeight * 0.25 },
+ exp: convertToUnorm8(presentColors[expect.topLeftColor]),
+ },
+ // Top-right.
+ {
+ coord: { x: kWidth * 0.75, y: kHeight * 0.25 },
+ exp: convertToUnorm8(presentColors[expect.topRightColor]),
+ },
+ // Bottom-left.
+ {
+ coord: { x: kWidth * 0.25, y: kHeight * 0.75 },
+ exp: convertToUnorm8(presentColors[expect.bottomLeftColor]),
+ },
+ // Bottom-right.
+ {
+ coord: { x: kWidth * 0.75, y: kHeight * 0.75 },
+ exp: convertToUnorm8(presentColors[expect.bottomRightColor]),
+ },
]);
-
- if (sourceType === 'VideoFrame') (source as VideoFrame).close();
});
});
-g.test('importExternalTexture,sampleWithRotationMetadata')
+g.test('importExternalTexture,sample_non_YUV_video_frame')
.desc(
`
-Tests that when importing an HTMLVideoElement/VideoFrame into a GPUExternalTexture, sampling from
-it will honor rotation metadata.
+Tests that we can import an VideoFrame with non-YUV pixel format into a GPUExternalTexture and sample it.
`
)
.params(u =>
u //
- .combineWithParams(checkNonStandardIsZeroCopyIfAvailable())
- .combine('sourceType', ['VideoElement', 'VideoFrame'] as const)
- .combineWithParams(kVideoRotationExpectations)
+ .combine('videoFrameFormat', ['RGBA', 'RGBX', 'BGRA', 'BGRX'] as const)
)
- .fn(async t => {
- const sourceType = t.params.sourceType;
- const videoElement = getVideoElement(t, t.params.videoName);
+ .fn(t => {
+ const { videoFrameFormat } = t.params;
- await startPlayingAndWaitForVideo(videoElement, async () => {
- const source =
- sourceType === 'VideoFrame'
- ? await getVideoFrameFromVideoElement(t, videoElement)
- : videoElement;
+ if (typeof VideoFrame === 'undefined') {
+ t.skip('WebCodec is not supported');
+ }
- const colorAttachment = t.device.createTexture({
- format: kFormat,
- size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
- usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
- });
+ const canvas = createCanvas(t, 'onscreen', kWidth, kHeight);
- const pipeline = createExternalTextureSamplingTestPipeline(t);
- const bindGroup = createExternalTextureSamplingTestBindGroup(
- t,
- t.params.checkNonStandardIsZeroCopy,
- source,
- pipeline
- );
+ const canvasContext = canvas.getContext('2d');
- const commandEncoder = t.device.createCommandEncoder();
- const passEncoder = commandEncoder.beginRenderPass({
- colorAttachments: [
- {
- view: colorAttachment.createView(),
- clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
- loadOp: 'clear',
- storeOp: 'store',
- },
- ],
- });
- passEncoder.setPipeline(pipeline);
- passEncoder.setBindGroup(0, bindGroup);
- passEncoder.draw(6);
- passEncoder.end();
- t.device.queue.submit([commandEncoder.finish()]);
+ if (canvasContext === null) {
+ t.skip(' onscreen canvas 2d context not available');
+ }
- // For validation, we sample a few pixels away from the edges to avoid compression
- // artifacts.
- t.expectSinglePixelComparisonsAreOkInTexture({ texture: colorAttachment }, [
- { coord: { x: kWidth * 0.25, y: kHeight * 0.25 }, exp: t.params._topLeftExpectation },
- { coord: { x: kWidth * 0.75, y: kHeight * 0.25 }, exp: t.params._topRightExpectation },
- { coord: { x: kWidth * 0.25, y: kHeight * 0.75 }, exp: t.params._bottomLeftExpectation },
- { coord: { x: kWidth * 0.75, y: kHeight * 0.75 }, exp: t.params._bottomRightExpectation },
- ]);
+ const ctx = canvasContext as CanvasRenderingContext2D;
+
+ const rectWidth = Math.floor(kWidth / 2);
+ const rectHeight = Math.floor(kHeight / 2);
+
+ // Red
+ ctx.fillStyle = `rgba(255, 0, 0, 1.0)`;
+ ctx.fillRect(0, 0, rectWidth, rectHeight);
+ // Lime
+ ctx.fillStyle = `rgba(0, 255, 0, 1.0)`;
+ ctx.fillRect(rectWidth, 0, kWidth - rectWidth, rectHeight);
+ // Blue
+ ctx.fillStyle = `rgba(0, 0, 255, 1.0)`;
+ ctx.fillRect(0, rectHeight, rectWidth, kHeight - rectHeight);
+ // Fuchsia
+ ctx.fillStyle = `rgba(255, 0, 255, 1.0)`;
+ ctx.fillRect(rectWidth, rectHeight, kWidth - rectWidth, kHeight - rectHeight);
+
+ const imageData = ctx.getImageData(0, 0, kWidth, kHeight);
+
+ // Create video frame with default color space 'srgb'
+ const frameInit: VideoFrameBufferInit = {
+ format: videoFrameFormat,
+ codedWidth: kWidth,
+ codedHeight: kHeight,
+ timestamp: 0,
+ };
+
+ const frame = new VideoFrame(imageData.data.buffer, frameInit);
+ let textureFormat: GPUTextureFormat = 'rgba8unorm';
+
+ if (videoFrameFormat === 'BGRA' || videoFrameFormat === 'BGRX') {
+ textureFormat = 'bgra8unorm';
+ }
- if (sourceType === 'VideoFrame') (source as VideoFrame).close();
+ const colorAttachment = t.device.createTexture({
+ format: textureFormat,
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+
+ const pipeline = createExternalTextureSamplingTestPipeline(t, textureFormat);
+ const bindGroup = createExternalTextureSamplingTestBindGroup(
+ t,
+ undefined /* checkNonStandardIsZeroCopy */,
+ frame,
+ pipeline,
+ 'srgb'
+ );
+
+ const commandEncoder = t.device.createCommandEncoder();
+ const passEncoder = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: colorAttachment.createView(),
+ clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store',
+ },
+ ],
});
+ passEncoder.setPipeline(pipeline);
+ passEncoder.setBindGroup(0, bindGroup);
+ passEncoder.draw(6);
+ passEncoder.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+
+ const expected = {
+ topLeft: new Uint8Array([255, 0, 0, 255]),
+ topRight: new Uint8Array([0, 255, 0, 255]),
+ bottomLeft: new Uint8Array([0, 0, 255, 255]),
+ bottomRight: new Uint8Array([255, 0, 255, 255]),
+ };
+
+ // For validation, we sample a few pixels away from the edges to avoid compression
+ // artifacts.
+ t.expectSinglePixelComparisonsAreOkInTexture({ texture: colorAttachment }, [
+ // Top-left.
+ {
+ coord: { x: kWidth * 0.25, y: kHeight * 0.25 },
+ exp: expected.topLeft,
+ },
+ // Top-right.
+ {
+ coord: { x: kWidth * 0.75, y: kHeight * 0.25 },
+ exp: expected.topRight,
+ },
+ // Bottom-left.
+ {
+ coord: { x: kWidth * 0.25, y: kHeight * 0.75 },
+ exp: expected.bottomLeft,
+ },
+ // Bottom-right.
+ {
+ coord: { x: kWidth * 0.75, y: kHeight * 0.75 },
+ exp: expected.bottomRight,
+ },
+ ]);
});
g.test('importExternalTexture,sampleWithVideoFrameWithVisibleRectParam')
@@ -281,10 +367,13 @@ parameters are present.
.params(u =>
u //
.combineWithParams(checkNonStandardIsZeroCopyIfAvailable())
- .combineWithParams(kVideoExpectations)
+ .combine('videoName', kVideoNames)
+ .combine('dstColorSpace', kPredefinedColorSpace)
)
.fn(async t => {
- const videoElement = getVideoElement(t, t.params.videoName);
+ const { videoName, dstColorSpace } = t.params;
+
+ const videoElement = getVideoElement(t, videoName);
await startPlayingAndWaitForVideo(videoElement, async () => {
const source = await getVideoFrameFromVideoElement(t, videoElement);
@@ -292,15 +381,24 @@ parameters are present.
// All tested videos are derived from an image showing yellow, red, blue or green in each
// quadrant. In this test we crop the video to each quadrant and check that desired color
// is sampled from each corner of the cropped image.
- const srcVideoHeight = 240;
- const srcVideoWidth = 320;
+ // visible rect clip applies on raw decoded frame, which defines based on video frame coded size.
+ const srcVideoHeight = source.codedHeight;
+ const srcVideoWidth = source.codedWidth;
+
+ const srcColorSpace = kVideoInfo[videoName].colorSpace;
+ const presentColors = kVideoExpectedColors[srcColorSpace][dstColorSpace];
+
+ // The test crops raw decoded videos first and then apply transform. Expectation should
+ // use coded colors as reference.
+ const expect = kVideoInfo[videoName].coded;
+
const cropParams = [
- // Top left (yellow)
+ // Top left
{
subRect: { x: 0, y: 0, width: srcVideoWidth / 2, height: srcVideoHeight / 2 },
- color: t.params._yellowExpectation,
+ color: convertToUnorm8(presentColors[expect.topLeftColor]),
},
- // Top right (red)
+ // Top right
{
subRect: {
x: srcVideoWidth / 2,
@@ -308,9 +406,9 @@ parameters are present.
width: srcVideoWidth / 2,
height: srcVideoHeight / 2,
},
- color: t.params._redExpectation,
+ color: convertToUnorm8(presentColors[expect.topRightColor]),
},
- // Bottom left (blue)
+ // Bottom left
{
subRect: {
x: 0,
@@ -318,9 +416,9 @@ parameters are present.
width: srcVideoWidth / 2,
height: srcVideoHeight / 2,
},
- color: t.params._blueExpectation,
+ color: convertToUnorm8(presentColors[expect.bottomLeftColor]),
},
- // Bottom right (green)
+ // Bottom right
{
subRect: {
x: srcVideoWidth / 2,
@@ -328,14 +426,12 @@ parameters are present.
width: srcVideoWidth / 2,
height: srcVideoHeight / 2,
},
- color: t.params._greenExpectation,
+ color: convertToUnorm8(presentColors[expect.bottomRightColor]),
},
];
for (const cropParam of cropParams) {
- // MAINTENANCE_TODO: remove cast with TypeScript 4.9.6+.
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const subRect = new VideoFrame(source as any, { visibleRect: cropParam.subRect });
+ const subRect = new VideoFrame(source, { visibleRect: cropParam.subRect });
const colorAttachment = t.device.createTexture({
format: kFormat,
@@ -348,7 +444,8 @@ parameters are present.
t,
t.params.checkNonStandardIsZeroCopy,
subRect,
- pipeline
+ pipeline,
+ dstColorSpace
);
const commandEncoder = t.device.createCommandEncoder();
@@ -387,22 +484,24 @@ g.test('importExternalTexture,compute')
.desc(
`
Tests that we can import an HTMLVideoElement/VideoFrame into a GPUExternalTexture and use it in a
-compute shader, for several combinations of video format and color space.
+compute shader, for several combinations of video format, video color spaces and dst color spaces.
`
)
.params(u =>
u //
.combineWithParams(checkNonStandardIsZeroCopyIfAvailable())
+ .combine('videoName', kVideoNames)
.combine('sourceType', ['VideoElement', 'VideoFrame'] as const)
- .combineWithParams(kVideoExpectations)
+ .combine('dstColorSpace', kPredefinedColorSpace)
)
.fn(async t => {
- const sourceType = t.params.sourceType;
+ const { videoName, sourceType, dstColorSpace } = t.params;
+
if (sourceType === 'VideoFrame' && typeof VideoFrame === 'undefined') {
t.skip('WebCodec is not supported');
}
- const videoElement = getVideoElement(t, t.params.videoName);
+ const videoElement = getVideoElement(t, videoName);
await startPlayingAndWaitForVideo(videoElement, async () => {
const source =
@@ -410,8 +509,8 @@ compute shader, for several combinations of video format and color space.
? await getVideoFrameFromVideoElement(t, videoElement)
: videoElement;
const externalTexture = t.device.importExternalTexture({
- /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
- source: source as any,
+ source,
+ colorSpace: dstColorSpace,
});
if (t.params.checkNonStandardIsZeroCopy) {
expectZeroCopyNonStandard(t, externalTexture);
@@ -422,29 +521,51 @@ compute shader, for several combinations of video format and color space.
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.STORAGE_BINDING,
});
+ // Use display size of VideoFrame and video size of HTMLVideoElement as frame size. These sizes are presenting size which
+ // apply transformation in video metadata if any.
+
const pipeline = t.device.createComputePipeline({
layout: 'auto',
compute: {
- // Shader loads 4 pixels near each corner, and then store them in a storage texture.
+ // Shader loads 4 pixels, and then store them in a storage texture.
module: t.device.createShaderModule({
code: `
+ override frameWidth : i32 = 0;
+ override frameHeight : i32 = 0;
@group(0) @binding(0) var t : texture_external;
@group(0) @binding(1) var outImage : texture_storage_2d<rgba8unorm, write>;
@compute @workgroup_size(1) fn main() {
- var yellow : vec4<f32> = textureLoad(t, vec2<i32>(80, 60));
+ let coordTopLeft = vec2<i32>(frameWidth / 4, frameHeight / 4);
+ let coordTopRight = vec2<i32>(frameWidth / 4 * 3, frameHeight / 4);
+ let coordBottomLeft = vec2<i32>(frameWidth / 4, frameHeight / 4 * 3);
+ let coordBottomRight = vec2<i32>(frameWidth / 4 * 3, frameHeight / 4 * 3);
+ var yellow : vec4<f32> = textureLoad(t, coordTopLeft);
textureStore(outImage, vec2<i32>(0, 0), yellow);
- var red : vec4<f32> = textureLoad(t, vec2<i32>(240, 60));
+ var red : vec4<f32> = textureLoad(t, coordTopRight);
textureStore(outImage, vec2<i32>(0, 1), red);
- var blue : vec4<f32> = textureLoad(t, vec2<i32>(80, 180));
+ var blue : vec4<f32> = textureLoad(t, coordBottomLeft);
textureStore(outImage, vec2<i32>(1, 0), blue);
- var green : vec4<f32> = textureLoad(t, vec2<i32>(240, 180));
+ var green : vec4<f32> = textureLoad(t, coordBottomRight);
textureStore(outImage, vec2<i32>(1, 1), green);
return;
}
`,
}),
entryPoint: 'main',
+
+ // Use display size of VideoFrame and video size of HTMLVideoElement as frame size. These sizes are presenting size which
+ // apply transformation in video metadata if any.
+ constants: {
+ frameWidth:
+ sourceType === 'VideoFrame'
+ ? (source as VideoFrame).displayWidth
+ : (source as HTMLVideoElement).videoWidth,
+ frameHeight:
+ sourceType === 'VideoFrame'
+ ? (source as VideoFrame).displayHeight
+ : (source as HTMLVideoElement).videoHeight,
+ },
},
});
@@ -464,17 +585,21 @@ compute shader, for several combinations of video format and color space.
pass.end();
t.device.queue.submit([encoder.finish()]);
+ const srcColorSpace = kVideoInfo[videoName].colorSpace;
+ const presentColors = kVideoExpectedColors[srcColorSpace][dstColorSpace];
+
+ // visible rect is whole frame, no clipping.
+ const expect = kVideoInfo[videoName].display;
+
t.expectSinglePixelComparisonsAreOkInTexture({ texture: outputTexture }, [
- // Top-left should be yellow.
- { coord: { x: 0, y: 0 }, exp: t.params._yellowExpectation },
- // Top-right should be red.
- { coord: { x: 0, y: 1 }, exp: t.params._redExpectation },
- // Bottom-left should be blue.
- { coord: { x: 1, y: 0 }, exp: t.params._blueExpectation },
- // Bottom-right should be green.
- { coord: { x: 1, y: 1 }, exp: t.params._greenExpectation },
+ // Top-left.
+ { coord: { x: 0, y: 0 }, exp: convertToUnorm8(presentColors[expect.topLeftColor]) },
+ // Top-right.
+ { coord: { x: 0, y: 1 }, exp: convertToUnorm8(presentColors[expect.topRightColor]) },
+ // Bottom-left.
+ { coord: { x: 1, y: 0 }, exp: convertToUnorm8(presentColors[expect.bottomLeftColor]) },
+ // Bottom-right.
+ { coord: { x: 1, y: 1 }, exp: convertToUnorm8(presentColors[expect.bottomRightColor]) },
]);
-
- if (sourceType === 'VideoFrame') (source as VideoFrame).close();
});
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_bgra8unorm.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_bgra8unorm.https.html
index c910c97b1d..a0068dbf7a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_bgra8unorm.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_bgra8unorm.https.html
@@ -14,6 +14,7 @@
<link rel="help" href="https://gpuweb.github.io/gpuweb/" />
<meta name="assert" content="WebGPU bgra8norm canvas with colorSpace set should be rendered correctly" />
<link rel="match" href="./ref/canvas_colorspace-ref.html" />
+ <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-9999999">
<script type="module">
import { runColorSpaceTest } from './canvas_colorspace.html.js';
runColorSpaceTest('bgra8unorm');
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba16float.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba16float.https.html
index 7f57858e49..b38fef9591 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba16float.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba16float.https.html
@@ -14,7 +14,7 @@
<link rel="help" href="https://gpuweb.github.io/gpuweb/" />
<meta name="assert" content="WebGPU rgba16float canvas with colorSpace set should be rendered correctly" />
<link rel="match" href="./ref/canvas_colorspace-ref.html" />
- <meta name=fuzzy content="maxDifference=1;totalPixels=8192">
+ <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-9999999">
<script type="module">
import { runColorSpaceTest } from './canvas_colorspace.html.js';
runColorSpaceTest('rgba16float');
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba8unorm.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba8unorm.https.html
index e57e04ef5c..404aed360c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba8unorm.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_colorspace_rgba8unorm.https.html
@@ -14,6 +14,7 @@
<link rel="help" href="https://gpuweb.github.io/gpuweb/" />
<meta name="assert" content="WebGPU rgba8unorm canvas with colorSpace set should be rendered correctly" />
<link rel="match" href="./ref/canvas_colorspace-ref.html" />
+ <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-9999999">
<script type="module">
import { runColorSpaceTest } from './canvas_colorspace.html.js';
runColorSpaceTest('rgba8unorm');
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html
index 70920dc0e6..5c3b888fee 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html
@@ -8,7 +8,7 @@
content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
/>
<link rel="match" href="./ref/canvas_composite_alpha_premultiplied-ref.html" />
- <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-400">
+ <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-9999999">
<style>
body { background-color: #F0E68C; }
#c-canvas { background-color: #8CF0E6; }
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html
index d12751fac2..81335296c0 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html
@@ -8,7 +8,7 @@
content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
/>
<link rel="match" href="./ref/canvas_composite_alpha_premultiplied-ref.html" />
- <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-400">
+ <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-9999999">
<style>
body { background-color: #F0E68C; }
#c-canvas { background-color: #8CF0E6; }
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_copy.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_copy.https.html
index ed722013c1..28e2553fed 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_copy.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_copy.https.html
@@ -8,7 +8,7 @@
content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
/>
<link rel="match" href="./ref/canvas_composite_alpha_premultiplied-ref.html" />
- <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-400">
+ <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-9999999">
<style>
body { background-color: #F0E68C; }
#c-canvas { background-color: #8CF0E6; }
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_draw.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_draw.https.html
index 8a028b168e..ca76fac7cd 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_draw.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba16float_premultiplied_draw.https.html
@@ -8,7 +8,7 @@
content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
/>
<link rel="match" href="./ref/canvas_composite_alpha_premultiplied-ref.html" />
- <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-400">
+ <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-9999999">
<style>
body { background-color: #F0E68C; }
#c-canvas { background-color: #8CF0E6; }
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html
index fa938aba41..7936e0b81c 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html
@@ -8,7 +8,7 @@
content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
/>
<link rel="match" href="./ref/canvas_composite_alpha_premultiplied-ref.html" />
- <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-400">
+ <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-9999999">
<style>
body { background-color: #F0E68C; }
#c-canvas { background-color: #8CF0E6; }
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html
index b62e71054c..da48abd2be 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html
@@ -8,7 +8,7 @@
content="WebGPU canvas should have correct orientation, components, scaling, filtering, color space"
/>
<link rel="match" href="./ref/canvas_composite_alpha_premultiplied-ref.html" />
- <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-400">
+ <meta name=fuzzy content="maxDifference=0-2;totalPixels=0-9999999">
<style>
body { background-color: #F0E68C; }
#c-canvas { background-color: #8CF0E6; }
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_image_rendering.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_image_rendering.https.html
index f51145645b..6a64b3da5d 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_image_rendering.https.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/canvas_image_rendering.https.html
@@ -5,11 +5,11 @@
<link rel="help" href="https://gpuweb.github.io/gpuweb/" />
<meta name="assert" content="WebGPU canvas with image-rendering set should be rendered correctly" />
<link rel="match" href="./ref/canvas_image_rendering-ref.html" />
- <canvas id="elem1" width="64" height="64" style="width: 99px; height: 99px;"></canvas>
- <canvas id="elem2" width="64" height="64" style="width: 99px; height: 99px; image-rendering: pixelated;"></canvas>
- <canvas id="elem3" width="64" height="64" style="width: 99px; height: 99px; image-rendering: crisp-edges"></canvas>
- <canvas id="elem4" width="64" height="64" style="width: 99px; height: 99px;"></canvas>
- <canvas id="elem5" width="64" height="64" style="width: 99px; height: 99px; image-rendering: pixelated;"></canvas>
- <canvas id="elem6" width="64" height="64" style="width: 99px; height: 99px; image-rendering: crisp-edges"></canvas>
+ <canvas id="elem1" width="64" height="64" style="width: 128px; height: 128px;"></canvas>
+ <canvas id="elem2" width="64" height="64" style="width: 128px; height: 128px; image-rendering: pixelated;"></canvas>
+ <canvas id="elem3" width="64" height="64" style="width: 128px; height: 128px; image-rendering: crisp-edges"></canvas>
+ <canvas id="elem4" width="64" height="64" style="width: 128px; height: 128px;"></canvas>
+ <canvas id="elem5" width="64" height="64" style="width: 128px; height: 128px; image-rendering: pixelated;"></canvas>
+ <canvas id="elem6" width="64" height="64" style="width: 128px; height: 128px; image-rendering: crisp-edges"></canvas>
<script type="module" src="canvas_image_rendering.html.js"></script>
</html>
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/delay_get_texture.html.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/delay_get_texture.html.ts
new file mode 100644
index 0000000000..a73216a34e
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/delay_get_texture.html.ts
@@ -0,0 +1,46 @@
+import { timeout } from '../../../common/util/timeout.js';
+import { takeScreenshotDelayed } from '../../../common/util/wpt_reftest_wait.js';
+
+function assert(condition: boolean, msg?: string | (() => string)): asserts condition {
+ if (!condition) {
+ throw new Error(msg && (typeof msg === 'string' ? msg : msg()));
+ }
+}
+
+void (async () => {
+ assert(
+ typeof navigator !== 'undefined' && navigator.gpu !== undefined,
+ 'No WebGPU implementation found'
+ );
+
+ const adapter = await navigator.gpu.requestAdapter();
+ assert(adapter !== null);
+ const device = await adapter.requestDevice();
+ assert(device !== null);
+
+ const canvas = document.getElementById('cvs0') as HTMLCanvasElement;
+ const ctx = canvas.getContext('webgpu') as unknown as GPUCanvasContext;
+ ctx.configure({
+ device,
+ format: navigator.gpu.getPreferredCanvasFormat(),
+ alphaMode: 'premultiplied',
+ });
+
+ timeout(() => {
+ const encoder = device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: ctx.getCurrentTexture().createView(),
+ clearValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store',
+ },
+ ],
+ });
+ pass.end();
+ device.queue.submit([encoder.finish()]);
+
+ takeScreenshotDelayed(50);
+ }, 100);
+})();
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/delay_get_texture.https.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/delay_get_texture.https.html
new file mode 100644
index 0000000000..054c352ac2
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/delay_get_texture.https.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>WebGPU delay getCurrentTexture</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <meta name="assert" content="WebGPU delay calling getCurrentTexture should be presented correctly" />
+ <link rel="match" href="./ref/delay_get_texture-ref.html" />
+ <canvas id="cvs0" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script type="module" src="delay_get_texture.html.js"></script>
+</html>
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/ref/canvas_image_rendering-ref.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/ref/canvas_image_rendering-ref.html
index f9eca704e8..56e3453c56 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/ref/canvas_image_rendering-ref.html
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/ref/canvas_image_rendering-ref.html
@@ -3,12 +3,12 @@
<title>WebGPU canvas_image_rendering (ref)</title>
<meta charset="utf-8" />
<link rel="help" href="https://gpuweb.github.io/gpuweb/" />
- <img id="elem1" width="64" height="64" style="width: 99px; height: 99px;">
- <img id="elem2" width="64" height="64" style="width: 99px; height: 99px; image-rendering: pixelated;">
- <img id="elem3" width="64" height="64" style="width: 99px; height: 99px; image-rendering: crisp-edges">
- <img id="elem4" width="64" height="64" style="width: 99px; height: 99px;">
- <img id="elem5" width="64" height="64" style="width: 99px; height: 99px; image-rendering: pixelated;">
- <img id="elem6" width="64" height="64" style="width: 99px; height: 99px; image-rendering: crisp-edges">
+ <img id="elem1" width="64" height="64" style="width: 128px; height: 128px;">
+ <img id="elem2" width="64" height="64" style="width: 128px; height: 128px; image-rendering: pixelated;">
+ <img id="elem3" width="64" height="64" style="width: 128px; height: 128px; image-rendering: crisp-edges">
+ <img id="elem4" width="64" height="64" style="width: 128px; height: 128px;">
+ <img id="elem5" width="64" height="64" style="width: 128px; height: 128px; image-rendering: pixelated;">
+ <img id="elem6" width="64" height="64" style="width: 128px; height: 128px; image-rendering: crisp-edges">
<script type="module">
import { takeScreenshotDelayed } from '../../../../common/util/wpt_reftest_wait.js';
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/ref/delay_get_texture-ref.html b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/ref/delay_get_texture-ref.html
new file mode 100644
index 0000000000..fcf485dbe1
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/reftests/ref/delay_get_texture-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+ <title>WebGPU delay getCurrentTexture (ref)</title>
+ <meta charset="utf-8" />
+ <link rel="help" href="https://gpuweb.github.io/gpuweb/" />
+ <canvas id="cvs0" width="20" height="20" style="width: 20px; height: 20px;"></canvas>
+ <script>
+ function draw(canvas) {
+ var c = document.getElementById(canvas);
+ var ctx = c.getContext('2d');
+ ctx.fillStyle = '#00FF00';
+ ctx.fillRect(0, 0, c.width, c.height);
+ }
+
+ draw('cvs0');
+ </script>
+</html>
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/util.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/util.ts
index 84ac6b31d1..56514b2203 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/util.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/util.ts
@@ -1,9 +1,10 @@
import { Fixture, SkipTestCase } from '../../common/framework/fixture.js';
import { getResourcePath } from '../../common/framework/resources.js';
-import { makeTable } from '../../common/util/data_tables.js';
+import { keysOf } from '../../common/util/data_tables.js';
import { timeout } from '../../common/util/timeout.js';
import { ErrorWithExtra, raceWithRejectOnTimeout } from '../../common/util/util.js';
import { GPUTest } from '../gpu_test.js';
+import { RGBA, srgbToDisplayP3 } from '../util/color_space_conversion.js';
declare global {
interface HTMLMediaElement {
@@ -13,113 +14,342 @@ declare global {
}
}
-export const kVideoInfo =
- /* prettier-ignore */ makeTable(
- ['mimeType' ] as const,
- [undefined ] as const, {
- // All video names
- 'four-colors-vp8-bt601.webm': ['video/webm; codecs=vp8' ],
- 'four-colors-theora-bt601.ogv': ['video/ogg; codecs=theora' ],
- 'four-colors-h264-bt601.mp4': ['video/mp4; codecs=avc1.4d400c'],
- 'four-colors-vp9-bt601.webm': ['video/webm; codecs=vp9' ],
- 'four-colors-vp9-bt709.webm': ['video/webm; codecs=vp9' ],
- 'four-colors-vp9-bt2020.webm': ['video/webm; codecs=vp9' ],
- 'four-colors-h264-bt601-rotate-90.mp4': ['video/mp4; codecs=avc1.4d400c'],
- 'four-colors-h264-bt601-rotate-180.mp4': ['video/mp4; codecs=avc1.4d400c'],
- 'four-colors-h264-bt601-rotate-270.mp4': ['video/mp4; codecs=avc1.4d400c'],
- } as const);
-export type VideoName = keyof typeof kVideoInfo;
+// MAINTENANCE_TODO: Uses raw floats as expectation in external_texture related cases has some diffs.
+// Remove this conversion utils and uses raw float data as expectation in external_textrue
+// related cases when resolve this.
+export function convertToUnorm8(expectation: Readonly<RGBA>): Uint8Array {
+ const rgba8Unorm = new Uint8ClampedArray(4);
+ rgba8Unorm[0] = Math.round(expectation.R * 255.0);
+ rgba8Unorm[1] = Math.round(expectation.G * 255.0);
+ rgba8Unorm[2] = Math.round(expectation.B * 255.0);
+ rgba8Unorm[3] = Math.round(expectation.A * 255.0);
+ return new Uint8Array(rgba8Unorm.buffer);
+}
+
+// MAINTENANCE_TODO: Add helper function for BT.601 and BT.709 to remove all magic numbers.
// Expectation values about converting video contents to sRGB color space.
// Source video color space affects expected values.
// The process to calculate these expected pixel values can be found:
// https://github.com/gpuweb/cts/pull/2242#issuecomment-1430382811
// and https://github.com/gpuweb/cts/pull/2242#issuecomment-1463273434
const kBt601PixelValue = {
- red: new Float32Array([0.972945567233341, 0.141794376683341, -0.0209589916711088, 1.0]),
- green: new Float32Array([0.248234279433399, 0.984810378661784, -0.0564701319494314, 1.0]),
- blue: new Float32Array([0.10159735826538, 0.135451122863674, 1.00262982899724, 1.0]),
- yellow: new Float32Array([0.995470750775951, 0.992742114518355, -0.0774291236205402, 1.0]),
-};
-
-function convertToUnorm8(expectation: Float32Array): Uint8Array {
- const unorm8 = new Uint8ClampedArray(expectation.length);
+ srgb: {
+ red: { R: 0.972945567233341, G: 0.141794376683341, B: -0.0209589916711088, A: 1.0 },
+ green: { R: 0.248234279433399, G: 0.984810378661784, B: -0.0564701319494314, A: 1.0 },
+ blue: { R: 0.10159735826538, G: 0.135451122863674, B: 1.00262982899724, A: 1.0 },
+ yellow: { R: 0.995470750775951, G: 0.992742114518355, B: -0.0701036235167653, A: 1.0 },
+ },
+} as const;
- for (let i = 0; i < expectation.length; ++i) {
- unorm8[i] = Math.round(expectation[i] * 255.0);
- }
+const kBt709PixelValue = {
+ srgb: {
+ red: { R: 1.0, G: 0.0, B: 0.0, A: 1.0 },
+ green: { R: 0.0, G: 1.0, B: 0.0, A: 1.0 },
+ blue: { R: 0.0, G: 0.0, B: 1.0, A: 1.0 },
+ yellow: { R: 1.0, G: 1.0, B: 0.0, A: 1.0 },
+ },
+} as const;
- return new Uint8Array(unorm8.buffer);
+function makeTable<Table extends { readonly [K: string]: {} }>({
+ table,
+}: {
+ table: Table;
+}): {
+ readonly [F in keyof Table]: {
+ readonly [K in keyof Table[F]]: Table[F][K];
+ };
+} {
+ return Object.fromEntries(
+ Object.entries(table).map(([k, row]) => [k, { ...row }])
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
+ ) as any;
}
-// kVideoExpectations uses unorm8 results
-const kBt601Red = convertToUnorm8(kBt601PixelValue.red);
-const kBt601Green = convertToUnorm8(kBt601PixelValue.green);
-const kBt601Blue = convertToUnorm8(kBt601PixelValue.blue);
-const kBt601Yellow = convertToUnorm8(kBt601PixelValue.yellow);
-
-export const kVideoExpectations = [
- {
- videoName: 'four-colors-vp8-bt601.webm',
- _redExpectation: kBt601Red,
- _greenExpectation: kBt601Green,
- _blueExpectation: kBt601Blue,
- _yellowExpectation: kBt601Yellow,
- },
- {
- videoName: 'four-colors-theora-bt601.ogv',
- _redExpectation: kBt601Red,
- _greenExpectation: kBt601Green,
- _blueExpectation: kBt601Blue,
- _yellowExpectation: kBt601Yellow,
- },
- {
- videoName: 'four-colors-h264-bt601.mp4',
- _redExpectation: kBt601Red,
- _greenExpectation: kBt601Green,
- _blueExpectation: kBt601Blue,
- _yellowExpectation: kBt601Yellow,
+// Video expected pixel value table. Finding expected pixel value
+// with video color space and dst color space.
+export const kVideoExpectedColors = makeTable({
+ table: {
+ bt601: {
+ 'display-p3': {
+ yellow: srgbToDisplayP3(kBt601PixelValue.srgb.yellow),
+ red: srgbToDisplayP3(kBt601PixelValue.srgb.red),
+ blue: srgbToDisplayP3(kBt601PixelValue.srgb.blue),
+ green: srgbToDisplayP3(kBt601PixelValue.srgb.green),
+ },
+ srgb: {
+ yellow: kBt601PixelValue.srgb.yellow,
+ red: kBt601PixelValue.srgb.red,
+ blue: kBt601PixelValue.srgb.blue,
+ green: kBt601PixelValue.srgb.green,
+ },
+ },
+ bt709: {
+ 'display-p3': {
+ yellow: srgbToDisplayP3(kBt709PixelValue.srgb.yellow),
+ red: srgbToDisplayP3(kBt709PixelValue.srgb.red),
+ blue: srgbToDisplayP3(kBt709PixelValue.srgb.blue),
+ green: srgbToDisplayP3(kBt709PixelValue.srgb.green),
+ },
+ srgb: {
+ yellow: kBt709PixelValue.srgb.yellow,
+ red: kBt709PixelValue.srgb.red,
+ blue: kBt709PixelValue.srgb.blue,
+ green: kBt709PixelValue.srgb.green,
+ },
+ },
},
- {
- videoName: 'four-colors-vp9-bt601.webm',
- _redExpectation: kBt601Red,
- _greenExpectation: kBt601Green,
- _blueExpectation: kBt601Blue,
- _yellowExpectation: kBt601Yellow,
- },
- {
- videoName: 'four-colors-vp9-bt709.webm',
- _redExpectation: new Uint8Array([255, 0, 0, 255]),
- _greenExpectation: new Uint8Array([0, 255, 0, 255]),
- _blueExpectation: new Uint8Array([0, 0, 255, 255]),
- _yellowExpectation: new Uint8Array([255, 255, 0, 255]),
- },
-] as const;
+} as const);
-export const kVideoRotationExpectations = [
- {
- videoName: 'four-colors-h264-bt601-rotate-90.mp4',
- _topLeftExpectation: kBt601Red,
- _topRightExpectation: kBt601Green,
- _bottomLeftExpectation: kBt601Yellow,
- _bottomRightExpectation: kBt601Blue,
- },
- {
- videoName: 'four-colors-h264-bt601-rotate-180.mp4',
- _topLeftExpectation: kBt601Green,
- _topRightExpectation: kBt601Blue,
- _bottomLeftExpectation: kBt601Red,
- _bottomRightExpectation: kBt601Yellow,
- },
- {
- videoName: 'four-colors-h264-bt601-rotate-270.mp4',
- _topLeftExpectation: kBt601Blue,
- _topRightExpectation: kBt601Yellow,
- _bottomLeftExpectation: kBt601Green,
- _bottomRightExpectation: kBt601Red,
+// MAINTENANCE_TODO: Add BT.2020 video in table.
+// Video container and codec defines several transform ops to apply to raw decoded frame to display.
+// Our test cases covers 'visible rect' and 'rotation'.
+// 'visible rect' is associated with the
+// video bitstream and should apply to the raw decoded frames before any transformation.
+// 'rotation' is associated with the track or presentation and should transform
+// the whole visible rect (e.g. 90-degree rotate makes visible rect of vertical video to horizontal)
+// The order to apply these transformations is below:
+
+// [raw decoded frame] ----visible rect clipping ---->[visible frame] ---rotation ---> present
+// ^ ^
+// | |
+// coded size display size
+// The table holds test videos meta infos, including mimeType to check browser compatibility
+// video color space, raw frame content layout and the frame displayed layout.
+export const kVideoInfo = makeTable({
+ table: {
+ 'four-colors-vp8-bt601.webm': {
+ mimeType: 'video/webm; codecs=vp8',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ 'four-colors-h264-bt601.mp4': {
+ mimeType: 'video/mp4; codecs=avc1.4d400c',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ 'four-colors-vp9-bt601.webm': {
+ mimeType: 'video/webm; codecs=vp9',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ 'four-colors-vp9-bt709.webm': {
+ mimeType: 'video/webm; codecs=vp9',
+ colorSpace: 'bt709',
+ coded: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ // video coded content has been rotate
+ 'four-colors-h264-bt601-rotate-90.mp4': {
+ mimeType: 'video/mp4; codecs=avc1.4d400c',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'red',
+ topRightColor: 'green',
+ bottomLeftColor: 'yellow',
+ bottomRightColor: 'blue',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ 'four-colors-h264-bt601-rotate-180.mp4': {
+ mimeType: 'video/mp4; codecs=avc1.4d400c',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'green',
+ topRightColor: 'blue',
+ bottomLeftColor: 'red',
+ bottomRightColor: 'yellow',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ 'four-colors-h264-bt601-rotate-270.mp4': {
+ mimeType: 'video/mp4; codecs=avc1.4d400c',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'blue',
+ topRightColor: 'yellow',
+ bottomLeftColor: 'green',
+ bottomRightColor: 'red',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ 'four-colors-vp9-bt601-rotate-90.mp4': {
+ mimeType: 'video/mp4; codecs=vp09.00.10.08',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'red',
+ topRightColor: 'green',
+ bottomLeftColor: 'yellow',
+ bottomRightColor: 'blue',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ 'four-colors-vp9-bt601-rotate-180.mp4': {
+ mimeType: 'video/mp4; codecs=vp09.00.10.08',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'green',
+ topRightColor: 'blue',
+ bottomLeftColor: 'red',
+ bottomRightColor: 'yellow',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ 'four-colors-vp9-bt601-rotate-270.mp4': {
+ mimeType: 'video/mp4; codecs=vp09.00.10.08',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'blue',
+ topRightColor: 'yellow',
+ bottomLeftColor: 'green',
+ bottomRightColor: 'red',
+ },
+ display: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ },
+ 'four-colors-h264-bt601-hflip.mp4': {
+ mimeType: 'video/mp4; codecs=avc1.4d400c',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ display: {
+ topLeftColor: 'red',
+ topRightColor: 'yellow',
+ bottomLeftColor: 'green',
+ bottomRightColor: 'blue',
+ },
+ },
+ 'four-colors-h264-bt601-vflip.mp4': {
+ mimeType: 'video/mp4; codecs=avc1.4d400c',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ display: {
+ topLeftColor: 'blue',
+ topRightColor: 'green',
+ bottomLeftColor: 'yellow',
+ bottomRightColor: 'red',
+ },
+ },
+ 'four-colors-vp9-bt601-hflip.mp4': {
+ mimeType: 'video/mp4; codecs=vp09.00.10.08',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ display: {
+ topLeftColor: 'red',
+ topRightColor: 'yellow',
+ bottomLeftColor: 'green',
+ bottomRightColor: 'blue',
+ },
+ },
+ 'four-colors-vp9-bt601-vflip.mp4': {
+ mimeType: 'video/mp4; codecs=vp09.00.10.08',
+ colorSpace: 'bt601',
+ coded: {
+ topLeftColor: 'yellow',
+ topRightColor: 'red',
+ bottomLeftColor: 'blue',
+ bottomRightColor: 'green',
+ },
+ display: {
+ topLeftColor: 'blue',
+ topRightColor: 'green',
+ bottomLeftColor: 'yellow',
+ bottomRightColor: 'red',
+ },
+ },
},
-] as const;
+} as const);
+type VideoName = keyof typeof kVideoInfo;
+export const kVideoNames: readonly VideoName[] = keysOf(kVideoInfo);
+
+export const kPredefinedColorSpace = ['display-p3', 'srgb'] as const;
/**
* Starts playing a video and waits for it to be consumable.
* Returns a promise which resolves after `callback` (which may be async) completes.
@@ -153,7 +383,12 @@ export function startPlayingAndWaitForVideo(
video.addEventListener(
'error',
- event => reject(new ErrorWithExtra('Video received "error" event', () => ({ event }))),
+ event =>
+ reject(
+ new ErrorWithExtra('Video received "error" event, message: ' + event.message, () => ({
+ event,
+ }))
+ ),
true
);
@@ -238,6 +473,7 @@ export async function getVideoFrameFromVideoElement(
const transformer: TransformStream = new TransformStream({
transform(videoFrame, _controller) {
videoTrack.stop();
+ test.trackForCleanup(videoFrame);
resolve(videoFrame);
},
flush(controller) {
@@ -267,6 +503,10 @@ export async function getVideoFrameFromVideoElement(
*
*/
export function getVideoElement(t: GPUTest, videoName: VideoName): HTMLVideoElement {
+ if (typeof HTMLVideoElement === 'undefined') {
+ t.skip('HTMLVideoElement not available');
+ }
+
const videoElement = document.createElement('video');
const videoInfo = kVideoInfo[videoName];
@@ -277,6 +517,8 @@ export function getVideoElement(t: GPUTest, videoName: VideoName): HTMLVideoElem
const videoUrl = getResourcePath(videoName);
videoElement.src = videoUrl;
+ t.trackForCleanup(videoElement);
+
return videoElement;
}
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.spec.ts
index 67f9f693be..14fe135633 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.spec.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.spec.ts
@@ -1,9 +1,13 @@
export const description = `
-Tests WebGPU is available in a worker.
+Tests WebGPU is available in a dedicated worker and a shared worker.
-Note: The CTS test can be run in a worker by passing in worker=1 as
-a query parameter. This test is specifically to check that WebGPU
-is available in a worker.
+Note: Any CTS test can be run in a worker by passing ?worker=dedicated, ?worker=shared,
+?worker=service as a query parameter. The tests in this file are specifically to check
+that WebGPU is available in each worker type. When run in combination with a ?worker flag,
+they will test workers created from other workers (where APIs exist to do so).
+
+TODO[2]: Figure out how to make these tests run in service workers (not actually
+important unless service workers gain the ability to launch other workers).
`;
import { Fixture } from '../../../common/framework/fixture.js';
@@ -12,24 +16,52 @@ import { assert } from '../../../common/util/util.js';
export const g = makeTestGroup(Fixture);
-function isNode(): boolean {
- return typeof process !== 'undefined' && process?.versions?.node !== undefined;
-}
+const isNode = typeof process !== 'undefined' && process?.versions?.node !== undefined;
+
+// [1]: we load worker_launcher dynamically because ts-node support
+// is using commonjs which doesn't support import.meta. Further,
+// we need to put the url in a string and pass the string to import
+// otherwise typescript tries to parse the file which again, fails.
+// worker_launcher.js is excluded in node.tsconfig.json.
+
+// [2]: That hack does not work in Service Workers.
+const isServiceWorker = globalThis.constructor.name === 'ServiceWorkerGlobalScope';
-g.test('worker')
- .desc(`test WebGPU is available in DedicatedWorkers and check for basic functionality`)
+g.test('dedicated_worker')
+ .desc(`test WebGPU is available in dedicated workers and check for basic functionality`)
.fn(async t => {
- if (isNode()) {
- t.skip('node does not support 100% compatible workers');
- return;
- }
- // Note: we load worker_launcher dynamically because ts-node support
- // is using commonjs which doesn't support import.meta. Further,
- // we need to put the url in a string add pass the string to import
- // otherwise typescript tries to parse the file which again, fails.
- // worker_launcher.js is excluded in node.tsconfig.json.
+ t.skipIf(isNode, 'node does not support 100% compatible workers');
+
+ t.skipIf(isServiceWorker, 'Service workers do not support this import() hack'); // [2]
const url = './worker_launcher.js';
- const { launchWorker } = await import(url);
- const result = await launchWorker();
+ const { launchDedicatedWorker } = await import(url); // [1]
+
+ const result = await launchDedicatedWorker();
+ assert(result.error === undefined, `should be no error from worker but was: ${result.error}`);
+ });
+
+g.test('shared_worker')
+ .desc(`test WebGPU is available in shared workers and check for basic functionality`)
+ .fn(async t => {
+ t.skipIf(isNode, 'node does not support 100% compatible workers');
+
+ t.skipIf(isServiceWorker, 'Service workers do not support this import() hack'); // [2]
+ const url = './worker_launcher.js';
+ const { launchSharedWorker } = await import(url); // [1]
+
+ const result = await launchSharedWorker();
+ assert(result.error === undefined, `should be no error from worker but was: ${result.error}`);
+ });
+
+g.test('service_worker')
+ .desc(`test WebGPU is available in service workers and check for basic functionality`)
+ .fn(async t => {
+ t.skipIf(isNode, 'node does not support 100% compatible workers');
+
+ t.skipIf(isServiceWorker, 'Service workers do not support this import() hack'); // [2]
+ const url = './worker_launcher.js';
+ const { launchServiceWorker } = await import(url); // [1]
+
+ const result = await launchServiceWorker();
assert(result.error === undefined, `should be no error from worker but was: ${result.error}`);
});
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.ts
index a3cf8064e2..033473d63a 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker.ts
@@ -1,6 +1,10 @@
import { getGPU, setDefaultRequestAdapterOptions } from '../../../common/util/navigator_gpu.js';
import { assert, objectEquals, iterRange } from '../../../common/util/util.js';
+// Should be WorkerGlobalScope, but importing lib "webworker" conflicts with lib "dom".
+/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
+declare const self: any;
+
async function basicTest() {
const adapter = await getGPU(null).requestAdapter();
assert(adapter !== null, 'Failed to get adapter.');
@@ -68,7 +72,7 @@ async function basicTest() {
device.destroy();
}
-self.onmessage = async (ev: MessageEvent) => {
+async function reportTestResults(this: MessagePort | Worker, ev: MessageEvent) {
const defaultRequestAdapterOptions: GPURequestAdapterOptions =
ev.data.defaultRequestAdapterOptions;
setDefaultRequestAdapterOptions(defaultRequestAdapterOptions);
@@ -79,5 +83,17 @@ self.onmessage = async (ev: MessageEvent) => {
} catch (err: unknown) {
error = (err as Error).toString();
}
- self.postMessage({ error });
+ this.postMessage({ error });
+}
+
+self.onmessage = (ev: MessageEvent) => {
+ void reportTestResults.call(ev.source || self, ev);
+};
+
+self.onconnect = (event: MessageEvent) => {
+ const port = event.ports[0];
+
+ port.onmessage = (ev: MessageEvent) => {
+ void reportTestResults.call(port, ev);
+ };
};
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker_launcher.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker_launcher.ts
index 72059eb99f..4b1d31ae49 100644
--- a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker_launcher.ts
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/worker/worker_launcher.ts
@@ -1,10 +1,15 @@
+import { SkipTestCase } from '../../../common/framework/fixture.js';
import { getDefaultRequestAdapterOptions } from '../../../common/util/navigator_gpu.js';
export type TestResult = {
error: String | undefined;
};
-export async function launchWorker() {
+export async function launchDedicatedWorker() {
+ if (typeof Worker === 'undefined') {
+ throw new SkipTestCase(`Worker undefined in context ${globalThis.constructor.name}`);
+ }
+
const selfPath = import.meta.url;
const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/'));
const workerPath = selfPathDir + '/worker.js';
@@ -16,3 +21,55 @@ export async function launchWorker() {
worker.postMessage({ defaultRequestAdapterOptions: getDefaultRequestAdapterOptions() });
return await promise;
}
+
+export async function launchSharedWorker() {
+ if (typeof SharedWorker === 'undefined') {
+ throw new SkipTestCase(`SharedWorker undefined in context ${globalThis.constructor.name}`);
+ }
+
+ const selfPath = import.meta.url;
+ const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/'));
+ const workerPath = selfPathDir + '/worker.js';
+ const worker = new SharedWorker(workerPath, { type: 'module' });
+
+ const port = worker.port;
+ const promise = new Promise<TestResult>(resolve => {
+ port.addEventListener('message', ev => resolve(ev.data as TestResult), { once: true });
+ });
+ port.start();
+ port.postMessage({
+ defaultRequestAdapterOptions: getDefaultRequestAdapterOptions(),
+ });
+ return await promise;
+}
+
+export async function launchServiceWorker() {
+ if (typeof navigator === 'undefined' || typeof navigator.serviceWorker === 'undefined') {
+ throw new SkipTestCase(
+ `navigator.serviceWorker undefined in context ${globalThis.constructor.name}`
+ );
+ }
+
+ const selfPath = import.meta.url;
+ const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/'));
+ const serviceWorkerPath = selfPathDir + '/worker.js';
+ const registration = await navigator.serviceWorker.register(serviceWorkerPath, {
+ type: 'module',
+ });
+ await registration.update();
+
+ const promise = new Promise<TestResult>(resolve => {
+ navigator.serviceWorker.addEventListener(
+ 'message',
+ ev => {
+ resolve(ev.data as TestResult);
+ void registration.unregister();
+ },
+ { once: true }
+ );
+ });
+ registration.active?.postMessage({
+ defaultRequestAdapterOptions: getDefaultRequestAdapterOptions(),
+ });
+ return await promise;
+}