summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/tests')
-rw-r--r--dom/webgpu/tests/cts/checkout/.github/pull_request_template.md1
-rw-r--r--dom/webgpu/tests/cts/checkout/.github/workflows/pr.yml11
-rw-r--r--dom/webgpu/tests/cts/checkout/.github/workflows/push.yml5
-rw-r--r--dom/webgpu/tests/cts/checkout/.gitignore1
-rw-r--r--dom/webgpu/tests/cts/checkout/Gruntfile.js210
-rw-r--r--dom/webgpu/tests/cts/checkout/cts.code-workspace5
-rw-r--r--dom/webgpu/tests/cts/checkout/docs/adding_timing_metadata.md95
-rw-r--r--dom/webgpu/tests/cts/checkout/docs/build.md55
-rw-r--r--dom/webgpu/tests/cts/checkout/docs/case_cache.md81
-rw-r--r--dom/webgpu/tests/cts/checkout/docs/fp_primer.md102
-rw-r--r--dom/webgpu/tests/cts/checkout/docs/intro/developing.md23
-rw-r--r--dom/webgpu/tests/cts/checkout/docs/terms.md2
-rw-r--r--dom/webgpu/tests/cts/checkout/package-lock.json3303
-rw-r--r--dom/webgpu/tests/cts/checkout/package.json15
-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
-rwxr-xr-xdom/webgpu/tests/cts/checkout/tools/gen_listings7
-rw-r--r--dom/webgpu/tests/cts/checkout/tools/gen_listings_and_webworkers8
-rwxr-xr-xdom/webgpu/tests/cts/checkout/tools/gen_version13
-rw-r--r--dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_chunked2sec.json1
-rw-r--r--dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_unchunked.json1
-rw-r--r--dom/webgpu/tests/cts/checkout/tools/server6
-rw-r--r--dom/webgpu/tests/cts/checkout_commit.txt2
-rw-r--r--dom/webgpu/tests/cts/vendor/Cargo.lock106
-rw-r--r--dom/webgpu/tests/cts/vendor/Cargo.toml2
-rw-r--r--dom/webgpu/tests/cts/vendor/src/fs.rs33
-rw-r--r--dom/webgpu/tests/cts/vendor/src/main.rs49
773 files changed, 57392 insertions, 17344 deletions
diff --git a/dom/webgpu/tests/cts/checkout/.github/pull_request_template.md b/dom/webgpu/tests/cts/checkout/.github/pull_request_template.md
index 7fadba0fc3..0ee7f37372 100644
--- a/dom/webgpu/tests/cts/checkout/.github/pull_request_template.md
+++ b/dom/webgpu/tests/cts/checkout/.github/pull_request_template.md
@@ -10,6 +10,7 @@ Issue: #<!-- Fill in the issue number here. See docs/intro/life_of.md -->
- [ ] All missing test coverage is tracked with "TODO" or `.unimplemented()`.
- [ ] New helpers are `/** documented */` and new helper files are found in `helper_index.txt`.
- [ ] Test behaves as expected in a WebGPU implementation. (If not passing, explain above.)
+- [ ] Test have be tested with compatibility mode validation enabled and behave as expected. (If not passing, explain above.)
**Requirements for [reviewer sign-off](https://github.com/gpuweb/cts/blob/main/docs/reviews.md):**
diff --git a/dom/webgpu/tests/cts/checkout/.github/workflows/pr.yml b/dom/webgpu/tests/cts/checkout/.github/workflows/pr.yml
index a398bf13ac..53144304bb 100644
--- a/dom/webgpu/tests/cts/checkout/.github/workflows/pr.yml
+++ b/dom/webgpu/tests/cts/checkout/.github/workflows/pr.yml
@@ -4,22 +4,31 @@ on:
pull_request:
branches: [main]
+concurrency:
+ group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
+ cancel-in-progress: true
+
jobs:
build:
runs-on: ubuntu-latest
+ timeout-minutes: 15
steps:
- uses: actions/checkout@v3
with:
persist-credentials: false
- uses: actions/setup-node@v3
with:
- node-version: "16.x"
+ node-version: '16.x'
- run: npm ci
+ - name: validating cache
+ run: npx grunt run:validate-cache
- run: npm test
- name: copy out-wpt to wpt tree
run: |
git clone --depth 2 https://github.com/web-platform-tests/wpt.git
rsync -av out-wpt/ wpt/webgpu
+ - name: adding wpt lint ignore rule for *.bin
+ run: 'echo "TRAILING WHITESPACE, INDENT TABS, CR AT EOL: *.bin" >> wpt/lint.ignore'
- name: test wpt lint
run: ./wpt lint
working-directory: ./wpt
diff --git a/dom/webgpu/tests/cts/checkout/.github/workflows/push.yml b/dom/webgpu/tests/cts/checkout/.github/workflows/push.yml
index 6aa7a34e04..f1645e120a 100644
--- a/dom/webgpu/tests/cts/checkout/.github/workflows/push.yml
+++ b/dom/webgpu/tests/cts/checkout/.github/workflows/push.yml
@@ -4,9 +4,14 @@ on:
push:
branches: [main]
+concurrency:
+ group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
+ cancel-in-progress: true
+
jobs:
build:
runs-on: ubuntu-latest
+ timeout-minutes: 15
steps:
- uses: actions/checkout@v2.3.1
with:
diff --git a/dom/webgpu/tests/cts/checkout/.gitignore b/dom/webgpu/tests/cts/checkout/.gitignore
index f115ad4f69..6a7a4a913a 100644
--- a/dom/webgpu/tests/cts/checkout/.gitignore
+++ b/dom/webgpu/tests/cts/checkout/.gitignore
@@ -2,6 +2,7 @@
.vscode/
# Build files
+/gen/
/out/
/out-wpt/
/out-node/
diff --git a/dom/webgpu/tests/cts/checkout/Gruntfile.js b/dom/webgpu/tests/cts/checkout/Gruntfile.js
index cf2207fcff..eba38704f3 100644
--- a/dom/webgpu/tests/cts/checkout/Gruntfile.js
+++ b/dom/webgpu/tests/cts/checkout/Gruntfile.js
@@ -3,6 +3,10 @@
/* eslint-disable no-console */
const timer = require('grunt-timer');
+const { spawnSync } = require('child_process');
+const path = require('path');
+
+const kAllSuites = ['webgpu', 'stress', 'manual', 'unittests', 'demo'];
module.exports = function (grunt) {
timer.init(grunt);
@@ -12,7 +16,7 @@ module.exports = function (grunt) {
pkg: grunt.file.readJSON('package.json'),
clean: {
- out: ['out/', 'out-wpt/', 'out-node/'],
+ out: ['gen/', 'out/', 'out-wpt/', 'out-node/'],
},
run: {
@@ -20,38 +24,38 @@ module.exports = function (grunt) {
cmd: 'node',
args: ['tools/gen_version'],
},
- 'generate-listings': {
- // Overwrites the listings.js files in out/. Must run before copy:out-wpt-generated;
- // must not run before run:build-out (if it is run).
+ 'generate-listings-and-webworkers': {
cmd: 'node',
- args: ['tools/gen_listings', 'out/', 'src/webgpu', 'src/stress', 'src/manual', 'src/unittests', 'src/demo'],
+ args: ['tools/gen_listings_and_webworkers', 'gen/', ...kAllSuites.map(s => 'src/' + s)],
},
validate: {
cmd: 'node',
- args: ['tools/validate', 'src/webgpu', 'src/stress', 'src/manual', 'src/unittests', 'src/demo'],
+ args: ['tools/validate', ...kAllSuites.map(s => 'src/' + s)],
+ },
+ 'generate-cache': {
+ // Note this generates files into the src/ directory (not the gen/ directory).
+ cmd: 'node',
+ args: ['tools/gen_cache', 'src/webgpu'],
},
'validate-cache': {
cmd: 'node',
- args: ['tools/gen_cache', 'out', 'src/webgpu', '--validate'],
+ args: ['tools/gen_cache', 'src/webgpu', '--validate'],
},
- 'generate-wpt-cts-html': {
+ 'write-out-wpt-cts-html': {
+ // Note this generates directly into the out-wpt/ directory rather than the gen/ directory.
cmd: 'node',
args: ['tools/gen_wpt_cts_html', 'tools/gen_wpt_cfg_unchunked.json'],
},
- 'generate-wpt-cts-html-chunked2sec': {
+ 'write-out-wpt-cts-html-chunked2sec': {
+ // Note this generates directly into the out-wpt/ directory rather than the gen/ directory.
cmd: 'node',
args: ['tools/gen_wpt_cts_html', 'tools/gen_wpt_cfg_chunked2sec.json'],
},
- 'generate-cache': {
- cmd: 'node',
- args: ['tools/gen_cache', 'out', 'src/webgpu'],
- },
unittest: {
cmd: 'node',
args: ['tools/run_node', 'unittests:*'],
},
'build-out': {
- // Must run before run:generate-listings, which will overwrite some files.
cmd: 'node',
args: [
'node_modules/@babel/cli/bin/babel',
@@ -59,6 +63,9 @@ module.exports = function (grunt) {
'--source-maps=true',
'--out-dir=out/',
'src/',
+ // These files will be generated, instead of compiled from TypeScript.
+ '--ignore=src/common/internal/version.ts',
+ '--ignore=src/*/listing.ts',
],
},
'build-out-wpt': {
@@ -75,7 +82,7 @@ module.exports = function (grunt) {
'--only=src/webgpu/',
// These files will be generated, instead of compiled from TypeScript.
'--ignore=src/common/internal/version.ts',
- '--ignore=src/webgpu/listing.ts',
+ '--ignore=src/*/listing.ts',
// These files are only used by non-WPT builds.
'--ignore=src/common/runtime/cmdline.ts',
'--ignore=src/common/runtime/server.ts',
@@ -110,6 +117,15 @@ module.exports = function (grunt) {
'--copy-files'
],
},
+ 'copy-assets-node': {
+ cmd: 'node',
+ args: [
+ 'node_modules/@babel/cli/bin/babel',
+ 'src/resources/',
+ '--out-dir=out-node/resources/',
+ '--copy-files'
+ ],
+ },
lint: {
cmd: 'node',
args: ['node_modules/eslint/bin/eslint', 'src/**/*.ts', '--max-warnings=0'],
@@ -139,34 +155,71 @@ module.exports = function (grunt) {
},
copy: {
- 'out-wpt-generated': {
+ 'gen-to-out': {
+ // Must run after generate-common and run:build-out.
+ files: [
+ { expand: true, dest: 'out/', cwd: 'gen', src: 'common/internal/version.js' },
+ { expand: true, dest: 'out/', cwd: 'gen', src: '*/**/*.js' },
+ ],
+ },
+ 'gen-to-out-wpt': {
+ // Must run after generate-common and run:build-out-wpt.
files: [
- // Must run after run:generate-version and run:generate-listings.
- { expand: true, cwd: 'out', src: 'common/internal/version.js', dest: 'out-wpt/' },
- { expand: true, cwd: 'out', src: 'webgpu/listing.js', dest: 'out-wpt/' },
+ { expand: true, dest: 'out-wpt/', cwd: 'gen', src: 'common/internal/version.js' },
+ { expand: true, dest: 'out-wpt/', cwd: 'gen', src: 'webgpu/**/*.js' },
],
},
- 'out-wpt-htmlfiles': {
+ 'htmlfiles-to-out': {
+ // Must run after run:build-out.
files: [
- { expand: true, cwd: 'src', src: 'webgpu/**/*.html', dest: 'out-wpt/' },
+ { expand: true, dest: 'out/', cwd: 'src', src: 'webgpu/**/*.html' },
+ ],
+ },
+ 'htmlfiles-to-out-wpt': {
+ // Must run after run:build-out-wpt.
+ files: [
+ { expand: true, dest: 'out-wpt/', cwd: 'src', src: 'webgpu/**/*.html' },
],
},
},
- ts: {
- check: {
- tsconfig: {
- tsconfig: 'tsconfig.json',
- passThrough: true,
- },
+ concurrent: {
+ 'write-out-wpt-cts-html-all': {
+ tasks: [
+ 'run:write-out-wpt-cts-html',
+ 'run:write-out-wpt-cts-html-chunked2sec',
+ ],
+ },
+ 'all-builds': {
+ tasks: [
+ 'build-standalone',
+ 'build-wpt',
+ 'run:build-out-node',
+ ],
+ },
+ 'all-checks': {
+ tasks: [
+ 'ts-check',
+ 'run:validate',
+ 'run:validate-cache',
+ 'run:unittest',
+ 'run:lint',
+ 'run:tsdoc-treatWarningsAsErrors',
+ ],
+ },
+ 'all-builds-and-checks': {
+ tasks: [
+ 'build-all', // Internally concurrent
+ 'concurrent:all-checks',
+ ],
},
},
});
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-copy');
+ grunt.loadNpmTasks('grunt-concurrent');
grunt.loadNpmTasks('grunt-run');
- grunt.loadNpmTasks('grunt-ts');
const helpMessageTasks = [];
function registerTaskAndAddToHelp(name, desc, deps) {
@@ -177,71 +230,96 @@ module.exports = function (grunt) {
helpMessageTasks.push({ name, desc });
}
- grunt.registerTask('build-standalone', 'Build out/ (no listings, no checks, no WPT)', [
+ grunt.registerTask('ts-check', function() {
+ spawnSync(path.join('node_modules', '.bin', 'tsc'), [
+ '--project',
+ 'tsconfig.json',
+ '--noEmit',
+ ], {
+ shell: true,
+ stdio: 'inherit',
+ });
+ });
+
+ grunt.registerTask('generate-common', 'Generate files into gen/ and src/', [
+ 'run:generate-version',
+ 'run:generate-listings-and-webworkers',
+ 'run:generate-cache',
+ ]);
+ grunt.registerTask('build-standalone', 'Build out/ (no checks; run after generate-common)', [
'run:build-out',
'run:copy-assets',
- 'run:generate-version',
+ 'copy:gen-to-out',
+ 'copy:htmlfiles-to-out',
]);
- grunt.registerTask('build-wpt', 'Build out-wpt/ (no checks; run after generate-listings)', [
+ grunt.registerTask('build-wpt', 'Build out-wpt/ (no checks; run after generate-common)', [
'run:build-out-wpt',
'run:copy-assets-wpt',
+ 'copy:gen-to-out-wpt',
+ 'copy:htmlfiles-to-out-wpt',
+ 'concurrent:write-out-wpt-cts-html-all',
'run:autoformat-out-wpt',
- 'run:generate-version',
- 'copy:out-wpt-generated',
- 'copy:out-wpt-htmlfiles',
- 'run:generate-wpt-cts-html',
- 'run:generate-wpt-cts-html-chunked2sec',
+ ]);
+ grunt.registerTask('build-node', 'Build out-node/ (no checks; run after generate-common)', [
+ 'run:build-out-node',
+ 'run:copy-assets-node',
+ ]);
+ grunt.registerTask('build-all', 'Build out*/ (no checks; run after generate-common)', [
+ 'concurrent:all-builds',
+ 'build-done-message',
]);
grunt.registerTask('build-done-message', () => {
- process.stderr.write('\nBuild completed! Running checks/tests');
+ grunt.log.writeln(`\
+=====================================================
+==== Build completed! Continuing checks/tests... ====
+=====================================================`);
});
- registerTaskAndAddToHelp('pre', 'Run all presubmit checks: standalone+wpt+typecheck+unittest+lint', [
+ grunt.registerTask('pre', ['all']);
+
+ registerTaskAndAddToHelp('all', 'Run all builds and checks', [
'clean',
- 'run:validate',
- 'run:validate-cache',
- 'build-standalone',
- 'run:generate-listings',
- 'build-wpt',
- 'run:build-out-node',
- 'build-done-message',
- 'ts:check',
- 'run:unittest',
- 'run:lint',
- 'run:tsdoc-treatWarningsAsErrors',
+ 'generate-common',
+ 'concurrent:all-builds-and-checks',
]);
- registerTaskAndAddToHelp('standalone', 'Build standalone and typecheck', [
+ registerTaskAndAddToHelp('standalone', 'Build standalone (out/) (no checks)', [
+ 'generate-common',
'build-standalone',
- 'run:generate-listings',
'build-done-message',
- 'run:validate',
- 'ts:check',
]);
- registerTaskAndAddToHelp('wpt', 'Build for WPT and typecheck', [
- 'run:generate-listings',
+ registerTaskAndAddToHelp('wpt', 'Build for WPT (out-wpt/) (no checks)', [
+ 'generate-common',
'build-wpt',
'build-done-message',
- 'run:validate',
- 'ts:check',
]);
- registerTaskAndAddToHelp('unittest', 'Build standalone, typecheck, and unittest', [
- 'standalone',
+ registerTaskAndAddToHelp('node', 'Build node (out-node/) (no checks)', [
+ 'generate-common',
+ 'build-node',
+ 'build-done-message',
+ ]);
+ registerTaskAndAddToHelp('checks', 'Run all checks (and build tsdoc)', [
+ 'concurrent:all-checks',
+ ]);
+ registerTaskAndAddToHelp('unittest', 'Just run unittests', [
'run:unittest',
]);
- registerTaskAndAddToHelp('check', 'Just typecheck', [
- 'ts:check',
+ registerTaskAndAddToHelp('typecheck', 'Just typecheck', [
+ 'ts-check',
+ ]);
+ registerTaskAndAddToHelp('tsdoc', 'Just build tsdoc', [
+ 'run:tsdoc',
]);
- registerTaskAndAddToHelp('serve', 'Serve out/ on 127.0.0.1:8080 (does NOT compile source)', ['run:serve']);
+ registerTaskAndAddToHelp('serve', 'Serve out/ (without building anything)', ['run:serve']);
registerTaskAndAddToHelp('fix', 'Fix lint and formatting', ['run:fix']);
- addExistingTaskToHelp('clean', 'Clean out/ and out-wpt/');
+ addExistingTaskToHelp('clean', 'Delete built and generated files');
grunt.registerTask('default', '', () => {
- console.error('\nAvailable tasks (see grunt --help for info):');
+ console.error('\nRecommended tasks:');
+ let nameColumnSize = Math.max(...helpMessageTasks.map(({ name }) => name.length));
for (const { name, desc } of helpMessageTasks) {
- console.error(`$ grunt ${name}`);
- console.error(` ${desc}`);
+ console.error(`$ grunt ${name.padEnd(nameColumnSize)} # ${desc}`);
}
});
};
diff --git a/dom/webgpu/tests/cts/checkout/cts.code-workspace b/dom/webgpu/tests/cts/checkout/cts.code-workspace
index 9c7320ce4b..d27f9ccc83 100644
--- a/dom/webgpu/tests/cts/checkout/cts.code-workspace
+++ b/dom/webgpu/tests/cts/checkout/cts.code-workspace
@@ -10,6 +10,11 @@
"path": "src/webgpu"
}
],
+ "extensions": {
+ "recommendations": [
+ "esbenp.prettier-vscode"
+ ]
+ },
"settings": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.detectIndentation": false,
diff --git a/dom/webgpu/tests/cts/checkout/docs/adding_timing_metadata.md b/dom/webgpu/tests/cts/checkout/docs/adding_timing_metadata.md
index fe32cead20..e251524177 100644
--- a/dom/webgpu/tests/cts/checkout/docs/adding_timing_metadata.md
+++ b/dom/webgpu/tests/cts/checkout/docs/adding_timing_metadata.md
@@ -6,14 +6,22 @@
The raw data may be edited manually, to add entries or change timing values.
-The **list** of tests must stay up to date, so it can be used by external
-tools. This is verified by presubmit checks.
+The list of tests in this file is **not** guaranteed to stay up to date.
+Use the generated `gen/*_variant_list*.json` if you need a complete list.
-The `subcaseMS` values are estimates. They can be set to 0 if for some reason
+The `subcaseMS` values are estimates. They can be set to 0 or omitted if for some reason
you can't estimate the time (or there's an existing test with a long name and
-slow subcases that would result in query strings that are too long), but this
-will produce a non-fatal warning. Avoid creating new warnings whenever
-possible. Any existing failures should be fixed (eventually).
+slow subcases that would result in query strings that are too long).
+It's OK if the number is estimated too high.
+
+These entries are estimates for the amount of time that subcases take to run,
+and are used as inputs into the WPT tooling to attempt to portion out tests into
+approximately same-sized chunks. High estimates are OK, they just may generate
+more chunks than necessary.
+
+To check for missing or 0 entries, run
+`tools/validate --print-metadata-warnings src/webgpu`
+and look at the resulting warnings.
### Performance
@@ -25,46 +33,25 @@ should still execute in under 5 seconds on lower-end computers.
## Problem
-When adding new tests to the CTS you may occasionally see an error like this
+When renaming or removing tests from the CTS you will see an error like this
when running `npm test` or `npm run standalone`:
```
-ERROR: Tests missing from listing_meta.json. Please add the new tests (set subcaseMS to 0 if you cannot estimate it):
- webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*
-
-/home/runner/work/cts/cts/src/common/util/util.ts:38
- throw new Error(msg && (typeof msg === 'string' ? msg : msg()));
- ^
-Error:
- at assert (/home/runner/work/cts/cts/src/common/util/util.ts:38:11)
- at crawl (/home/runner/work/cts/cts/src/common/tools/crawl.ts:155:11)
-Warning: non-zero exit code 1
- Use --force to continue.
-
-Aborted due to warnings.
+ERROR: Non-existent tests found in listing_meta.json. Please update:
+ webgpu:api,operation,adapter,requestAdapter:old_test_that_got_renamed:*
```
-What this error message is trying to tell us, is that there is no entry for
-`webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*` in
-`src/webgpu/listing_meta.json`.
+## Solution
-These entries are estimates for the amount of time that subcases take to run,
-and are used as inputs into the WPT tooling to attempt to portion out tests into
-approximately same-sized chunks.
+This means there is a stale line in `src/webgpu/listing_meta.json` that needs
+to be deleted, or updated to match the rename that you did.
-If a value has been defaulted to 0 by someone, you will see warnings like this:
-
-```
-...
-WARNING: subcaseMS≤0 found in listing_meta.json (allowed, but try to avoid):
- webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*
-...
-```
+## Problem
-These messages should be resolved by adding appropriate entries to the JSON
-file.
+You run `tools/validate --print-metadata-warnings src/webgpu`
+and want to fix the warnings.
-## Solution 1 (manual, best for simple tests)
+## Solution 1 (manual, best for one-off updates of simple tests)
If you're developing new tests and need to update this file, it is sometimes
easiest to do so manually. Run your tests under your usual development workflow
@@ -82,30 +69,18 @@ these values, though they do require some manual intervention. The rest of this
doc will be a walkthrough of running these tools.
Timing data can be captured in bulk and "merged" into this file using
-the `merge_listing_times` tool. This is useful when a large number of tests
+the `merge_listing_times` tool. This is
+This is useful when a large number of tests
change or otherwise a lot of tests need to be updated, but it also automates the
manual steps above.
The tool can also be used without any inputs to reformat `listing_meta.json`.
Please read the help message of `merge_listing_times` for more information.
-### Placeholder Value
-
-If your development workflow requires a clean build, the first step is to add a
-placeholder value for entry to `src/webgpu/listing_meta.json`, since there is a
-chicken-and-egg problem for updating these values.
-
-```
- "webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*": { "subcaseMS": 0 },
-```
-
-(It should have a value of 0, since later tooling updates the value if the newer
-value is higher.)
-
### Websocket Logger
The first tool that needs to be run is `websocket-logger`, which receives data
-on a WebSocket channel to capture timing data when CTS is run. This
+on a WebSocket channel at `localhost:59497` to capture timing data when CTS is run. This
should be run in a separate process/terminal, since it needs to stay running
throughout the following steps.
@@ -125,10 +100,19 @@ Writing to wslog-2023-09-12T18-57-34.txt
...
```
+See also [tools/websocket-logger/README.md](../tools/websocket-logger/README.md).
+
### Running CTS
Now we need to run the specific cases in CTS that we need to time.
-This should be possible under any development workflow (as long as its runtime environment, like Node, supports WebSockets), but the most well-tested way is using the standalone web runner.
+
+This should be possible under any development workflow by logging through a
+side-channel (as long as its runtime environment, like Node, supports WebSockets).
+Regardless of development workflow, you need to enable logToWebSocket flag
+(`?log_to_web_socket=1` in browser, `--log-to-web-socket` on command line, or
+just hack it in by switching the default in `options.ts`).
+
+The most well-tested way to do this is using the standalone web runner.
This requires serving the CTS locally. In the project root:
@@ -141,7 +125,7 @@ Once this is started you can then direct a WebGPU enabled browser to the
specific CTS entry and run the tests, for example:
```
-http://localhost:8080/standalone/?q=webgpu:shader,execution,expression,binary,af_matrix_addition:matrix:*
+http://localhost:8080/standalone/?log_to_web_socket=1&q=webgpu:*
```
If the tests have a high variance in runtime, you can run them multiple times.
@@ -156,8 +140,9 @@ This can be done using the following command:
```
tools/merge_listing_times webgpu -- tools/websocket-logger/wslog-2023-09-12T18-57-34.txt
+tools/merge_listing_times webgpu -- tools/websocket-logger/wslog-*.txt
```
-where the text file is the result file from websocket-logger.
+Or, you can point it to one of the log files from a specific invocation of websocket-logger.
Now you just need to commit the pending diff in your repo.
diff --git a/dom/webgpu/tests/cts/checkout/docs/build.md b/dom/webgpu/tests/cts/checkout/docs/build.md
index 2d7b2f968c..d786bbc18c 100644
--- a/dom/webgpu/tests/cts/checkout/docs/build.md
+++ b/dom/webgpu/tests/cts/checkout/docs/build.md
@@ -1,16 +1,48 @@
# Building
Building the project is not usually needed for local development.
-However, for exports to WPT, or deployment (https://gpuweb.github.io/cts/),
+However, for exports to WPT, NodeJS, [or deployment](https://gpuweb.github.io/cts/),
files can be pre-generated.
-The project builds into two directories:
+## Build types
-- `out/`: Built framework and test files, needed to run standalone or command line.
-- `out-wpt/`: Build directory for export into WPT. Contains:
- - An adapter for running WebGPU CTS tests under WPT
- - A copy of the needed files from `out/`
- - A copy of any `.html` test cases from `src/`
+The project can be built several different ways, each with a different output directory:
+
+### 0. on-the-fly builds (no output directory)
+
+Use `npm run start` to launch a server that live-compiles everything as needed.
+Use `tools/run_node` and other tools to run under `ts-node` which compiles at runtime.
+
+### 1. `out` directory
+
+**Built with**: `npm run standalone`
+
+**Serve locally with**: `npx grunt serve`
+
+**Used for**: Static deployment of the CTS, primarily for [gpuweb.github.io/cts](https://gpuweb.github.io/cts/).
+
+### 2. `out-wpt` directory
+
+**Built with**: `npm run wpt`
+
+**Used for**: Deploying into [Web Platform Tests](https://web-platform-tests.org/). See [below](#export-to-wpt) for more information.
+
+Contains:
+
+- An adapter for running WebGPU CTS tests under WPT
+- A copy of the needed files from `out/`
+- A copy of any `.html` test cases from `src/`
+
+### 3. `out-node` directory
+
+**Built with**: `npm run node`
+
+**Used for**: Running NodeJS tools, if you want to specifically avoid the live-compilation overhead of the `tools/` versions, or are running on a deployment which no longer has access to `ts-node` (which is a build-time dependency). For example:
+
+- `node out-node/common/runtime/cmdline.js` ([source](../src/common/runtime/cmdline.ts)) - A command line interface test runner
+- `node out-node/common/runtime/server.js` ([source](../src/common/runtime/server.ts)) - An HTTP server for executing CTS tests with a REST interface
+
+## Testing
To build and run all pre-submit checks (including type and lint checks and
unittests), use:
@@ -25,15 +57,6 @@ For checks only:
npm run check
```
-For a quicker iterative build:
-
-```sh
-npm run standalone
-```
-
-## Run
-
-To serve the built files (rather than using the dev server), run `npx grunt serve`.
## Export to WPT
diff --git a/dom/webgpu/tests/cts/checkout/docs/case_cache.md b/dom/webgpu/tests/cts/checkout/docs/case_cache.md
new file mode 100644
index 0000000000..c3ba8718b5
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/docs/case_cache.md
@@ -0,0 +1,81 @@
+# Case Cache
+
+The WebGPU CTS contains many tests that check that the results of an operation
+fall within limits defined by the WebGPU and WGSL specifications. The
+computation of these allowed limits can be very expensive to calculate, however
+the values do not vary by platform or device, and can be precomputed and reused
+for multiple CTS runs.
+
+## File cache
+
+To speed up execution of the CTS, the CTS git repo holds holds pre-computed
+test cases, generated from `*.cache.ts` files and serialized in a set of binary
+files under [`src/resources/cache`](../src/resources/cache).
+
+These files are regenerated by [`src/common/tools/gen_cache.ts`](../src/common/tools/gen_cache.ts)
+which can be run with `npx grunt run:generate-cache`.
+This tool is automatically run by the various Grunt build commands.
+
+As generating the cache is expensive (hence why we build it ahead of time!) the
+cache generation tool will only re-build the cache files it believes may be out
+of date. To determine which files it needs to rebuild, the tool calculates a
+hash of all the transitive source TypeScript files that are used to build the
+output, and compares this hash to the hash stored in
+[`src/resources/cache/hashes.json`](`../src/resources/cache/hashes.json`). Only
+those cache files with differing hashes are rebuilt.
+
+Transitive imports easily grow, and these can cause unnecessary rebuilds of the cache.
+To help avoid unnecessary rebuilds, files that are known to not be used by the cache can be
+annotated with a `MUST_NOT_BE_IMPORTED_BY_DATA_CACHE` comment anywhere in the file. If a file with
+this comment is transitively imported by a `.cache.ts` file, then the cache generation tool will
+error with a trace of the imports from the `.cache.ts` file to the file with this comment.
+
+The cache files are copied from [`src/resources/cache`](../src/resources/cache)
+to the `resources/cache` subdirectory of the
+[`out` and `out-node` build directories](build.md#build-types), so the runner
+can load these cache files.
+
+The GitHub presubmit checks will error if the cache files or
+[`hashes.json`](`../src/resources/cache/hashes.json`) need updating.
+
+## In memory cache
+
+If a cache file cannot be found, then the [`CaseCache`](../src/webgpu/shader/execution/expression/case_cache.ts)
+will build the cases during CTS execution and store the results in an in-memory LRU cache.
+
+## Using the cache
+
+To add test cases to the cache:
+
+1. Create a new <code><i>my_file</i>.cache.ts</code> file.
+
+2. In that file, import `makeCaseCache` from [`'case_cache.js'`](../src/webgpu/shader/execution/expression/case_cache.ts);
+
+```ts
+import { makeCaseCache } from '../case_cache.js'; // your relative path may vary
+```
+
+3. Declare an exported global variable with the name `d`, assigned with the return value of `makeCaseCache()`:
+
+```ts
+export const d = makeCaseCache('unique/path/of/your/cache/file', {
+ // Declare any number of fields that build the test cases
+ name_of_your_case: () => {
+ return fullI32Range().map(e => { // example case builder
+ return { input: i32(e), expected: i32(-e) };
+ });
+ },
+});
+```
+
+4. To use the cached cases in a <code><i>my_file</i>.spec.ts</code> file, import `d` from <code><i>my_file</i>.cache.js</code>, and use `d.get();`
+
+```ts
+import { d } from './my_file.cache.js';
+
+const cases = await d.get('name_of_your_case');
+// cases will either be loaded from the cache file, loaded from the in-memory
+// LRU, or built on the fly.
+```
+
+5. Run `npx grunt run generate-cache` to generate the new cache file.
diff --git a/dom/webgpu/tests/cts/checkout/docs/fp_primer.md b/dom/webgpu/tests/cts/checkout/docs/fp_primer.md
index a8302fb461..769657c8f8 100644
--- a/dom/webgpu/tests/cts/checkout/docs/fp_primer.md
+++ b/dom/webgpu/tests/cts/checkout/docs/fp_primer.md
@@ -39,7 +39,8 @@ A floating point number system defines
- Arithmetic operations on those representatives, trying to approximate the
ideal operations on real numbers.
-The cardinality mismatch alone implies that any floating point number system necessarily loses information.
+The cardinality mismatch alone implies that any floating point number system
+necessarily loses information.
This means that not all numbers in the bounds can be exactly represented as a
floating point value.
@@ -114,7 +115,7 @@ Implementations may assume that infinities are not present. When an evaluation
at runtime would produce an infinity, an indeterminate value is produced
instead.
-When a value goes out of bounds for a specific precision there are special
+When a value goes out-of-bounds for a specific precision there are special
rounding rules that apply. If it is 'near' the edge of finite values for that
precision, it is considered to be near-overflowing, and the implementation may
choose to round it to the edge value or the appropriate infinity. If it is not
@@ -163,7 +164,7 @@ the rules for compile time execution will be discussed below.)
Signaling NaNs are treated as quiet NaNs in the WGSL spec. And quiet NaNs have
the same "may-convert-to-indeterminate-value" behaviour that infinities have, so
-for the purpose of the CTS they are handled by the infinite/out of bounds logic
+for the purpose of the CTS they are handled by the infinite/out-of-bounds logic
normally.
## Notation/Terminology
@@ -231,14 +232,20 @@ referred to as the beginning of the interval and `b` as the end of the interval.
When talking about intervals, this doc and the code endeavours to avoid using
the term **range** to refer to the span of values that an interval covers,
-instead using the term bounds to avoid confusion of terminology around output of
-operations.
+instead using the term **endpoints** to avoid confusion of terminology around
+output of operations.
+
+The term **endpoints** is generally used to refer to the conceptual numeric
+spaces, i.e. f32 or abstract float.
+
+Thus a specific interval can have **endpoints** that are either in or out of
+bounds for a specific floating point precision.
## Accuracy
As mentioned above floating point numbers are not able to represent all the
-possible values over their bounds, but instead represent discrete values in that
-interval, and approximate the remainder.
+possible values over their range, but instead represent discrete values in that
+space, and approximate the remainder.
Additionally, floating point numbers are not evenly distributed over the real
number line, but instead are more densely clustered around zero, with the space
@@ -398,7 +405,7 @@ That would be very inefficient though and make your reviewer sad to read.
For mapping intervals to intervals the key insight is that we only need to be
concerned with the extrema of the operation in the interval, since the
-acceptance interval is the bounds of the possible outputs.
+acceptance interval is defined by largest and smallest of the possible outputs.
In more precise terms:
```
@@ -538,6 +545,65 @@ This algorithmically looks something like this:
Return division result
```
+### Out of Bounds
+When calculating inherited intervals, if a intermediate calculation goes out of
+bounds this will flow through to later calculations, even if a later calculation
+would pull the result back inbounds.
+
+For example, `fp.positive.max + fp.positive.max - fp.positive.max` could be
+simplified to just `fp.positive.max` before execution, but it would also be
+valid for an implementation to naively perform left to right evaluation. Thus
+the addition would produce an intermediate value of `2 * fp.positive.max`. Again
+the implementation may hoist intermediate calculation to a higher precision to
+avoid overflow here, but is not required to. So a conforming implementation at
+this point may just return any value since the calculation when out of bounds.
+Thus the execution tests in the CTS should accept any value returned, so the
+case is just effectively confirming the computation completes.
+
+When looking at validation tests there is some subtleties about out of bounds
+behaviour, specifically how far out of bounds the value went that will influence
+the expected results, which will be discussed in more detail below.
+
+#### Vectors and Matrices
+The above discussion about inheritance of out of bounds intervals assumed scalar
+calculations, so all the result intervals were dependent on the input intervals,
+so if an out-of-bounds input occurred naturally all the output values were
+effectively out of bounds.
+
+For vector and matrix operations, this is not always true. Operations on these
+data structures can either define an element-wise mapping, where for each output
+element the result is calculated by executing a scalar operation on a input
+element (sometimes referred to as component-wise), or where the operation is
+defined such the output elements are dependent on the entire input.
+
+For concrete examples, constant scaling (`c * vec` of `c * mat`) is an
+element-wise operation, because one can define a simple mapping
+`o[i]` = `c * i[i]`, where the ith output only depends on the ith input.
+
+A non-element-wise operation would be something like cross product of vectors
+or the determinant of a matrix, where each output element is dependent on
+multiple input elements.
+
+For component-wise operations, out of bounds-ness flows through per element,
+i.e. if the ith input element was considered to be have gone out of bounds, then
+the ith output is considered to have too also regardless of the operation
+performed. Thus an input may be a mixture of out of bounds elements & inbounds
+elements, and produce another mixture, assuming the operation being performed
+itself does not push elements out of bounds.
+
+For non-component-wise operations, out of bounds-ness flows through the entire
+operation, i.e. if any of the input elements is out of bounds, then all the
+output elements are considered to be out of bounds. Additionally, if the
+calculation for any of the elements in output goes out of bounds, then the
+entire output is considered to have gone out of bounds, even if other individual
+elements stayed inbounds.
+
+For some non-element-wise operations one could define mappings for individual
+output elements that do not depend on all the input elements and consider only
+if those inputs that are used, but for the purposes of WGSL and the CTS, OOB
+inheritance is not so finely defined as to consider the difference between using
+some and all the input elements for non-element-wise operations.
+
## Compile vs Run Time Evaluation
The above discussions have been primarily agnostic to when and where a
@@ -553,14 +619,14 @@ These are compile vs run time, and CPU vs GPU. Broadly speaking compile time
execution happens on the host CPU, and run time evaluation occurs on a dedicated
GPU.
-(Software graphics implementations like WARP and SwiftShader technically break this by
-being a software emulation of a GPU that runs on the CPU, but conceptually one can
-think of these implementations being a type of GPU in this context, since it has
-similar constraints when it comes to precision, etc.)
+(Software graphics implementations like WARP and SwiftShader technically break
+this by being a software emulation of a GPU that runs on the CPU, but
+conceptually one can think of these implementations being a type of GPU in this
+context, since it has similar constraints when it comes to precision, etc.)
Compile time evaluation is execution that occurs when setting up a shader
module, i.e. when compiling WGSL to a platform specific shading language. It is
-part of resolving values for things like constants, and occurs once before the
+part of resolving values for things like constants, and occurs once, before the
shader is run by the caller. It includes constant evaluation and override
evaluation. All AbstractFloat operations are compile time evaluated.
@@ -623,7 +689,7 @@ near-overflow vs far-overflow behaviour. Thankfully this can be broken down into
a case by case basis based on where an interval falls.
Assuming `X`, is the well-defined result of an operation, i.e. not indeterminate
-due to the operation isn't defined for the inputs:
+due to the operation not being defined for the inputs:
| Region | | Result |
|------------------------------|------------------------------------------------------|--------------------------------|
@@ -643,7 +709,9 @@ behaviour in this region as rigorously defined nor tested, so fully testing
here would likely find lots of issues that would just need to be mitigated in
the CTS.
-Currently, we choose to avoid testing validation of near-overflow scenarios.
+Currently, we have chosen to not test validation of near-overflow scenarios to
+avoid this complexity. If this becomes a significant source of bugs and/or
+incompatibility between implementations this can be revisited in the future.
### Additional Technical Limitations
@@ -652,7 +720,7 @@ the theoretical world that the intervals being used for testing are infinitely
precise, when in actuality they are implemented by the ECMAScript `number` type,
which is implemented as a f64 value.
-For the vast majority of cases, even out of bounds and overflow, this is
+For the vast majority of cases, even out-of-bounds and overflow, this is
sufficient. There is one small slice where this breaks down. Specifically if
the result just outside the finite range by less than 1 f64 ULP of the edge
value. An example of this is `2 ** -11 + f32.max`. This will be between `f32.max`
@@ -752,7 +820,7 @@ const_assert upper > foo(x) // Result was above the acceptance interval
```
where lower and upper would actually be string replaced with literals for the
-bounds of the acceptance interval when generating the shader text.
+endpoints of the acceptance interval when generating the shader text.
This approach has a number of limitations that made it unacceptable for the CTS.
First, how errors are reported is a pain to debug. Someone working with the CTS
diff --git a/dom/webgpu/tests/cts/checkout/docs/intro/developing.md b/dom/webgpu/tests/cts/checkout/docs/intro/developing.md
index 5b1aeed36d..0016c2c048 100644
--- a/dom/webgpu/tests/cts/checkout/docs/intro/developing.md
+++ b/dom/webgpu/tests/cts/checkout/docs/intro/developing.md
@@ -34,6 +34,11 @@ the standalone runner.)
Note: The first load of a test suite may take some time as generating the test suite listing can
take a few seconds.
+## Documentation
+
+In addition to the documentation pages you're reading, there is TSDoc documentation.
+Start at the [helper index](https://gpuweb.github.io/cts/docs/tsdoc/).
+
## Standalone Test Runner / Test Plan Viewer
**The standalone test runner also serves as a test plan viewer.**
@@ -43,7 +48,7 @@ You can use this to preview how your test plan will appear.
You can view different suites (webgpu, unittests, stress, etc.) or different subtrees of
the test suite.
-- `http://localhost:8080/standalone/` (defaults to `?runnow=0&worker=0&debug=0&q=webgpu:*`)
+- `http://localhost:8080/standalone/` (defaults to `?runnow=0&debug=0&q=webgpu:*`)
- `http://localhost:8080/standalone/?q=unittests:*`
- `http://localhost:8080/standalone/?q=unittests:basic:*`
@@ -51,7 +56,9 @@ The following url parameters change how the harness runs:
- `runnow=1` runs all matching tests on page load.
- `debug=1` enables verbose debug logging from tests.
-- `worker=1` runs the tests on a Web Worker instead of the main thread.
+- `worker=dedicated` (or `worker` or `worker=1`) runs the tests on a dedicated worker instead of the main thread.
+- `worker=shared` runs the tests on a shared worker instead of the main thread.
+- `worker=service` runs the tests on a service worker instead of the main thread.
- `power_preference=low-power` runs most tests passing `powerPreference: low-power` to `requestAdapter`
- `power_preference=high-performance` runs most tests passing `powerPreference: high-performance` to `requestAdapter`
@@ -112,15 +119,17 @@ Opening a pull request will automatically notify reviewers.
To make the review process smoother, once a reviewer has started looking at your change:
- Avoid major additions or changes that would be best done in a follow-up PR.
-- Avoid rebases (`git rebase`) and force pushes (`git push -f`). These can make
- it difficult for reviewers to review incremental changes as GitHub often cannot
+- Avoid deleting commits that have already been reviewed, which occurs when using
+ rebases (`git rebase`) and force pushes (`git push -f`). These can make
+ it difficult for reviewers to review incremental changes as GitHub usually cannot
view a useful diff across a rebase. If it's necessary to resolve conflicts
with upstream changes, use a merge commit (`git merge`) and don't include any
- consequential changes in the merge, so a reviewer can skip over merge commits
+ unnecessary changes in the merge, so that a reviewer can skip over merge commits
when working through the individual commits in the PR.
-- When you address a review comment, mark the thread as "Resolved".
-Pull requests will (usually) be landed with the "Squash and merge" option.
+ The "Create a merge commit" merge option is disabled, so `main` history always
+ remains linear (no merge commits). PRs are usually landed using "Squash and merge".
+- When you address a review comment, mark the thread as "Resolved".
### TODOs
diff --git a/dom/webgpu/tests/cts/checkout/docs/terms.md b/dom/webgpu/tests/cts/checkout/docs/terms.md
index 032639be57..0dc6f0ca17 100644
--- a/dom/webgpu/tests/cts/checkout/docs/terms.md
+++ b/dom/webgpu/tests/cts/checkout/docs/terms.md
@@ -111,7 +111,7 @@ Each Suite has one Listing File (`suite/listing.[tj]s`), containing a list of th
in the suite.
In `src/suite/listing.ts`, this is computed dynamically.
-In `out/suite/listing.js`, the listing has been pre-baked (by `tools/gen_listings`).
+In `out/suite/listing.js`, the listing has been pre-baked (by `tools/gen_listings_and_webworkers`).
**Type:** Once `import`ed, `ListingFile`
diff --git a/dom/webgpu/tests/cts/checkout/package-lock.json b/dom/webgpu/tests/cts/checkout/package-lock.json
index 361ee369cd..83dbb0a58e 100644
--- a/dom/webgpu/tests/cts/checkout/package-lock.json
+++ b/dom/webgpu/tests/cts/checkout/package-lock.json
@@ -24,7 +24,7 @@
"@types/serve-index": "^1.9.3",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
- "@webgpu/types": "^0.1.38",
+ "@webgpu/types": "^0.1.40",
"ansi-colors": "4.1.3",
"babel-plugin-add-header-comment": "^1.0.3",
"babel-plugin-const-enum": "^1.2.0",
@@ -37,11 +37,11 @@
"express": "^4.18.2",
"grunt": "^1.6.1",
"grunt-cli": "^1.4.3",
+ "grunt-concurrent": "^3.0.0",
"grunt-contrib-clean": "^2.0.1",
"grunt-contrib-copy": "^1.0.0",
"grunt-run": "^0.8.1",
"grunt-timer": "^0.6.0",
- "grunt-ts": "^6.0.0-beta.22",
"gts": "^5.2.0",
"http-server": "^14.1.1",
"morgan": "^1.10.0",
@@ -1523,9 +1523,9 @@
"dev": true
},
"node_modules/@webgpu/types": {
- "version": "0.1.38",
- "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.38.tgz",
- "integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==",
+ "version": "0.1.40",
+ "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.40.tgz",
+ "integrity": "sha512-/BBkHLS6/eQjyWhY2H7Dx5DHcVrS2ICj9owvSRdgtQT6KcafLZA86tPze0xAOsd4FbsYKCUBUQyNi87q7gV7kw==",
"dev": true
},
"node_modules/abbrev": {
@@ -1672,33 +1672,6 @@
"sprintf-js": "~1.0.2"
}
},
- "node_modules/arr-diff": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
- "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/arr-flatten": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
- "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/arr-union": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
- "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/array-buffer-byte-length": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
@@ -1764,15 +1737,6 @@
"node": ">=8"
}
},
- "node_modules/array-unique": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
- "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/array.prototype.findlastindex": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz",
@@ -1858,39 +1822,12 @@
"node": ">=0.10.0"
}
},
- "node_modules/assign-symbols": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
- "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/async": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
"dev": true
},
- "node_modules/async-each": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
- "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==",
- "dev": true
- },
- "node_modules/atob": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
- "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
- "dev": true,
- "bin": {
- "atob": "bin/atob.js"
- },
- "engines": {
- "node": ">= 4.5.0"
- }
- },
"node_modules/available-typed-arrays": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
@@ -1929,74 +1866,6 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
- "node_modules/base": {
- "version": "0.11.2",
- "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
- "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
- "dev": true,
- "dependencies": {
- "cache-base": "^1.0.1",
- "class-utils": "^0.3.5",
- "component-emitter": "^1.2.1",
- "define-property": "^1.0.0",
- "isobject": "^3.0.1",
- "mixin-deep": "^1.2.0",
- "pascalcase": "^0.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/base/node_modules/define-property": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
- "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
- "dev": true,
- "dependencies": {
- "is-descriptor": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/base/node_modules/is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/base/node_modules/is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/base/node_modules/is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "dependencies": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/bash-color": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/bash-color/-/bash-color-0.0.3.tgz",
@@ -2039,16 +1908,6 @@
"node": ">=8"
}
},
- "node_modules/bindings": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
- "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
- "dev": true,
- "optional": true,
- "dependencies": {
- "file-uri-to-path": "1.0.0"
- }
- },
"node_modules/body-parser": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
@@ -2199,26 +2058,6 @@
"node": ">= 0.8"
}
},
- "node_modules/cache-base": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
- "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
- "dev": true,
- "dependencies": {
- "collection-visit": "^1.0.0",
- "component-emitter": "^1.2.1",
- "get-value": "^2.0.6",
- "has-value": "^1.0.0",
- "isobject": "^3.0.1",
- "set-value": "^2.0.0",
- "to-object-path": "^0.3.0",
- "union-value": "^1.0.0",
- "unset-value": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/call-bind": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
@@ -2335,21 +2174,6 @@
"fsevents": "~2.3.2"
}
},
- "node_modules/class-utils": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
- "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
- "dev": true,
- "dependencies": {
- "arr-union": "^3.1.0",
- "define-property": "^0.2.5",
- "isobject": "^3.0.0",
- "static-extend": "^0.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
@@ -2371,19 +2195,6 @@
"node": ">= 10"
}
},
- "node_modules/collection-visit": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
- "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
- "dev": true,
- "dependencies": {
- "map-visit": "^1.0.0",
- "object-visit": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -2408,12 +2219,6 @@
"node": ">=0.1.90"
}
},
- "node_modules/component-emitter": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
- "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
- "dev": true
- },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2482,15 +2287,6 @@
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
"dev": true
},
- "node_modules/copy-descriptor": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
- "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -2526,33 +2322,6 @@
"node": ">= 8"
}
},
- "node_modules/csproj2ts": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/csproj2ts/-/csproj2ts-1.1.0.tgz",
- "integrity": "sha512-sk0RTT51t4lUNQ7UfZrqjQx7q4g0m3iwNA6mvyh7gLsgQYvwKzfdyoAgicC9GqJvkoIkU0UmndV9c7VZ8pJ45Q==",
- "dev": true,
- "dependencies": {
- "es6-promise": "^4.1.1",
- "lodash": "^4.17.4",
- "semver": "^5.4.1",
- "xml2js": "^0.4.19"
- }
- },
- "node_modules/csproj2ts/node_modules/es6-promise": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
- "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
- "dev": true
- },
- "node_modules/csproj2ts/node_modules/semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
- "dev": true,
- "bin": {
- "semver": "bin/semver"
- }
- },
"node_modules/d": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
@@ -2620,15 +2389,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/decode-uri-component": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
- "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
- "dev": true,
- "engines": {
- "node": ">=0.10"
- }
- },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -2822,18 +2582,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/define-property": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
- "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
- "dev": true,
- "dependencies": {
- "is-descriptor": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -2862,27 +2610,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/detect-indent": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
- "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=",
- "dev": true,
- "dependencies": {
- "repeating": "^2.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/detect-newline": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz",
- "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -2916,6 +2643,18 @@
"node": ">=6.0.0"
}
},
+ "node_modules/duplexify": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
+ "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
+ "dev": true,
+ "dependencies": {
+ "end-of-stream": "^1.0.0",
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0",
+ "stream-shift": "^1.0.0"
+ }
+ },
"node_modules/duration": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/duration/-/duration-0.2.2.tgz",
@@ -2953,6 +2692,15 @@
"node": ">= 0.8"
}
},
+ "node_modules/end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -3081,12 +2829,6 @@
"es6-symbol": "^3.1.1"
}
},
- "node_modules/es6-promise": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-0.1.2.tgz",
- "integrity": "sha1-8RLCn+paCZhTn8tqL9IUQ9KPBfc=",
- "dev": true
- },
"node_modules/es6-symbol": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
@@ -3701,39 +3443,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/expand-brackets": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
- "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
- "dev": true,
- "dependencies": {
- "debug": "^2.3.3",
- "define-property": "^0.2.5",
- "extend-shallow": "^2.0.1",
- "posix-character-classes": "^0.1.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/expand-brackets/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/expand-brackets/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
- "dev": true
- },
"node_modules/expand-tilde": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
@@ -3874,18 +3583,6 @@
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"dev": true
},
- "node_modules/extend-shallow": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
- "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
- "dev": true,
- "dependencies": {
- "is-extendable": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/external-editor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -3900,75 +3597,6 @@
"node": ">=4"
}
},
- "node_modules/extglob": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
- "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
- "dev": true,
- "dependencies": {
- "array-unique": "^0.3.2",
- "define-property": "^1.0.0",
- "expand-brackets": "^2.1.4",
- "extend-shallow": "^2.0.1",
- "fragment-cache": "^0.2.1",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/extglob/node_modules/define-property": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
- "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
- "dev": true,
- "dependencies": {
- "is-descriptor": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/extglob/node_modules/is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/extglob/node_modules/is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/extglob/node_modules/is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "dependencies": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -4051,13 +3679,6 @@
"integrity": "sha1-peeo/7+kk7Q7kju9TKiaU7Y7YSs=",
"dev": true
},
- "node_modules/file-uri-to-path": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
- "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
- "dev": true,
- "optional": true
- },
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -4258,18 +3879,6 @@
"node": ">= 0.6"
}
},
- "node_modules/fragment-cache": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
- "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
- "dev": true,
- "dependencies": {
- "map-cache": "^0.2.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@@ -4402,15 +4011,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/get-value": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
- "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/getobject": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz",
@@ -4565,12 +4165,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/graceful-fs": {
- "version": "4.2.9",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
- "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
- "dev": true
- },
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -4636,6 +4230,33 @@
"nopt": "bin/nopt.js"
}
},
+ "node_modules/grunt-concurrent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/grunt-concurrent/-/grunt-concurrent-3.0.0.tgz",
+ "integrity": "sha512-AgXtjUJESHEGeGX8neL3nmXBTHSj1QC48ABQ3ng2/vjuSBpDD8gKcVHSlXP71pFkIR8TQHf+eomOx6OSYSgfrA==",
+ "dev": true,
+ "dependencies": {
+ "arrify": "^2.0.1",
+ "async": "^3.1.0",
+ "indent-string": "^4.0.0",
+ "pad-stream": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "peerDependencies": {
+ "grunt": ">=1"
+ }
+ },
+ "node_modules/grunt-concurrent/node_modules/arrify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
+ "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/grunt-contrib-clean": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-2.0.1.tgz",
@@ -4908,344 +4529,6 @@
"node": ">= 0.8.x"
}
},
- "node_modules/grunt-ts": {
- "version": "6.0.0-beta.22",
- "resolved": "https://registry.npmjs.org/grunt-ts/-/grunt-ts-6.0.0-beta.22.tgz",
- "integrity": "sha512-g9e+ZImQ7W38dfpwhp0+GUltXWidy3YGPfIA/IyGL5HMv6wmVmMMoSgscI5swhs2HSPf8yAvXAAJbwrouijoRg==",
- "dev": true,
- "dependencies": {
- "chokidar": "^2.0.4",
- "csproj2ts": "^1.1.0",
- "detect-indent": "^4.0.0",
- "detect-newline": "^2.1.0",
- "es6-promise": "~0.1.1",
- "jsmin2": "^1.2.1",
- "lodash": "~4.17.10",
- "ncp": "0.5.1",
- "rimraf": "2.2.6",
- "semver": "^5.3.0",
- "strip-bom": "^2.0.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- },
- "peerDependencies": {
- "grunt": "^1.0.0 || ^0.4.0",
- "typescript": ">=1"
- }
- },
- "node_modules/grunt-ts/node_modules/anymatch": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
- "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
- "dev": true,
- "dependencies": {
- "micromatch": "^3.1.4",
- "normalize-path": "^2.1.1"
- }
- },
- "node_modules/grunt-ts/node_modules/anymatch/node_modules/normalize-path": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
- "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
- "dev": true,
- "dependencies": {
- "remove-trailing-separator": "^1.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/binary-extensions": {
- "version": "1.13.1",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
- "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/braces": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
- "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
- "dev": true,
- "dependencies": {
- "arr-flatten": "^1.1.0",
- "array-unique": "^0.3.2",
- "extend-shallow": "^2.0.1",
- "fill-range": "^4.0.0",
- "isobject": "^3.0.1",
- "repeat-element": "^1.1.2",
- "snapdragon": "^0.8.1",
- "snapdragon-node": "^2.0.1",
- "split-string": "^3.0.2",
- "to-regex": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/chokidar": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
- "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
- "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies",
- "dev": true,
- "dependencies": {
- "anymatch": "^2.0.0",
- "async-each": "^1.0.1",
- "braces": "^2.3.2",
- "glob-parent": "^3.1.0",
- "inherits": "^2.0.3",
- "is-binary-path": "^1.0.0",
- "is-glob": "^4.0.0",
- "normalize-path": "^3.0.0",
- "path-is-absolute": "^1.0.0",
- "readdirp": "^2.2.1",
- "upath": "^1.1.1"
- },
- "optionalDependencies": {
- "fsevents": "^1.2.7"
- }
- },
- "node_modules/grunt-ts/node_modules/define-property": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
- "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
- "dev": true,
- "dependencies": {
- "is-descriptor": "^1.0.2",
- "isobject": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/fill-range": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
- "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
- "dev": true,
- "dependencies": {
- "extend-shallow": "^2.0.1",
- "is-number": "^3.0.0",
- "repeat-string": "^1.6.1",
- "to-regex-range": "^2.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/fsevents": {
- "version": "1.2.13",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
- "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
- "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.",
- "dev": true,
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "dependencies": {
- "bindings": "^1.5.0",
- "nan": "^2.12.1"
- },
- "engines": {
- "node": ">= 4.0"
- }
- },
- "node_modules/grunt-ts/node_modules/glob-parent": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
- "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
- "dev": true,
- "dependencies": {
- "is-glob": "^3.1.0",
- "path-dirname": "^1.0.0"
- }
- },
- "node_modules/grunt-ts/node_modules/glob-parent/node_modules/is-glob": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
- "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
- "dev": true,
- "dependencies": {
- "is-extglob": "^2.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/is-binary-path": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
- "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
- "dev": true,
- "dependencies": {
- "binary-extensions": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "dependencies": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "dependencies": {
- "is-plain-object": "^2.0.4"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/is-number": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
- "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
- "dev": true,
- "dependencies": {
- "kind-of": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/is-number/node_modules/kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/micromatch": {
- "version": "3.1.10",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
- "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
- "dev": true,
- "dependencies": {
- "arr-diff": "^4.0.0",
- "array-unique": "^0.3.2",
- "braces": "^2.3.1",
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "extglob": "^2.0.4",
- "fragment-cache": "^0.2.1",
- "kind-of": "^6.0.2",
- "nanomatch": "^1.2.9",
- "object.pick": "^1.3.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/micromatch/node_modules/extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "dependencies": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/grunt-ts/node_modules/readdirp": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
- "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
- "dev": true,
- "dependencies": {
- "graceful-fs": "^4.1.11",
- "micromatch": "^3.1.10",
- "readable-stream": "^2.0.2"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/grunt-ts/node_modules/rimraf": {
- "version": "2.2.6",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.6.tgz",
- "integrity": "sha1-xZWXVpsU2VatKcrMQr3d9fDqT0w=",
- "dev": true,
- "bin": {
- "rimraf": "bin.js"
- }
- },
- "node_modules/grunt-ts/node_modules/semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
- "dev": true,
- "bin": {
- "semver": "bin/semver"
- }
- },
- "node_modules/grunt-ts/node_modules/to-regex-range": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
- "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
- "dev": true,
- "dependencies": {
- "is-number": "^3.0.0",
- "repeat-string": "^1.6.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/grunt/node_modules/glob": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
@@ -5830,69 +5113,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/has-value": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
- "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
- "dev": true,
- "dependencies": {
- "get-value": "^2.0.6",
- "has-values": "^1.0.0",
- "isobject": "^3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/has-values": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
- "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
- "dev": true,
- "dependencies": {
- "is-number": "^3.0.0",
- "kind-of": "^4.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/has-values/node_modules/is-number": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
- "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
- "dev": true,
- "dependencies": {
- "kind-of": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/has-values/node_modules/is-number/node_modules/kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/has-values/node_modules/kind-of": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
- "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
- "dev": true,
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/hasown": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
@@ -6320,30 +5540,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/is-accessor-descriptor": {
- "version": "0.1.6",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
- "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
- "dev": true,
- "dependencies": {
- "kind-of": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-accessor-descriptor/node_modules/kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/is-array-buffer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
@@ -6404,12 +5600,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-buffer": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
- "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
- "dev": true
- },
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -6434,30 +5624,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-data-descriptor": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
- "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
- "dev": true,
- "dependencies": {
- "kind-of": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-data-descriptor/node_modules/kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/is-date-object": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
@@ -6473,29 +5639,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-descriptor": {
- "version": "0.1.6",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
- "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
- "dev": true,
- "dependencies": {
- "is-accessor-descriptor": "^0.1.6",
- "is-data-descriptor": "^0.1.4",
- "kind-of": "^5.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-descriptor/node_modules/kind-of": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
- "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/is-docker": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
@@ -6511,15 +5654,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/is-extendable": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
- "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -6529,18 +5663,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/is-finite": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz",
- "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -6755,12 +5877,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/is-utf8": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
- "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
- "dev": true
- },
"node_modules/is-weakref": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
@@ -6861,12 +5977,6 @@
"node": ">=4"
}
},
- "node_modules/jsmin2": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/jsmin2/-/jsmin2-1.2.1.tgz",
- "integrity": "sha1-iPvi+/dfCpH2YCD9mBzWk/S/5X4=",
- "dev": true
- },
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
@@ -7071,18 +6181,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/map-visit": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
- "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
- "dev": true,
- "dependencies": {
- "object-visit": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/marked": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
@@ -7271,31 +6369,6 @@
"node": ">= 6"
}
},
- "node_modules/mixin-deep": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
- "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
- "dev": true,
- "dependencies": {
- "for-in": "^1.0.2",
- "is-extendable": "^1.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/mixin-deep/node_modules/is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "dependencies": {
- "is-plain-object": "^2.0.4"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/morgan": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
@@ -7348,111 +6421,6 @@
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
"dev": true
},
- "node_modules/nan": {
- "version": "2.15.0",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
- "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
- "dev": true,
- "optional": true
- },
- "node_modules/nanomatch": {
- "version": "1.2.13",
- "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
- "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
- "dev": true,
- "dependencies": {
- "arr-diff": "^4.0.0",
- "array-unique": "^0.3.2",
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "fragment-cache": "^0.2.1",
- "is-windows": "^1.0.2",
- "kind-of": "^6.0.2",
- "object.pick": "^1.3.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/nanomatch/node_modules/define-property": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
- "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
- "dev": true,
- "dependencies": {
- "is-descriptor": "^1.0.2",
- "isobject": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/nanomatch/node_modules/extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "dependencies": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/nanomatch/node_modules/is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/nanomatch/node_modules/is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/nanomatch/node_modules/is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "dependencies": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/nanomatch/node_modules/is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "dependencies": {
- "is-plain-object": "^2.0.4"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -7465,15 +6433,6 @@
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
"dev": true
},
- "node_modules/ncp": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.5.1.tgz",
- "integrity": "sha1-dDmFMW49tFkoG1hxaehFc1oFQ58=",
- "dev": true,
- "bin": {
- "ncp": "bin/ncp"
- }
- },
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -7558,32 +6517,6 @@
"node": ">=8"
}
},
- "node_modules/object-copy": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
- "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
- "dev": true,
- "dependencies": {
- "copy-descriptor": "^0.1.0",
- "define-property": "^0.2.5",
- "kind-of": "^3.0.3"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-copy/node_modules/kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/object-inspect": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
@@ -7602,18 +6535,6 @@
"node": ">= 0.4"
}
},
- "node_modules/object-visit": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
- "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
- "dev": true,
- "dependencies": {
- "isobject": "^3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/object.assign": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
@@ -7865,6 +6786,20 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/pad-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pad-stream/-/pad-stream-2.0.0.tgz",
+ "integrity": "sha512-3QeQw19K48BQzUGZ9dEf/slX5Jbfy5ZeBTma2XICketO7kFNK7omF00riVcecOKN+DSiJZcK2em1eYKaVOeXKg==",
+ "dev": true,
+ "dependencies": {
+ "pumpify": "^1.3.3",
+ "split2": "^2.1.1",
+ "through2": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -7927,21 +6862,6 @@
"node": ">= 0.8"
}
},
- "node_modules/pascalcase": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
- "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/path-dirname": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
- "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
- "dev": true
- },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -8103,15 +7023,6 @@
"mkdirp": "bin/cmd.js"
}
},
- "node_modules/posix-character-classes": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
- "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -8167,6 +7078,27 @@
"node": ">= 0.10"
}
},
+ "node_modules/pump": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
+ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
+ "dev": true,
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/pumpify": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
+ "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
+ "dev": true,
+ "dependencies": {
+ "duplexify": "^3.6.0",
+ "inherits": "^2.0.3",
+ "pump": "^2.0.0"
+ }
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -8434,44 +7366,6 @@
"node": ">=8"
}
},
- "node_modules/regex-not": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
- "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
- "dev": true,
- "dependencies": {
- "extend-shallow": "^3.0.2",
- "safe-regex": "^1.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/regex-not/node_modules/extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "dependencies": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/regex-not/node_modules/is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "dependencies": {
- "is-plain-object": "^2.0.4"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/regexp.prototype.flags": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
@@ -8501,42 +7395,6 @@
"url": "https://github.com/sponsors/mysticatea"
}
},
- "node_modules/remove-trailing-separator": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
- "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
- "dev": true
- },
- "node_modules/repeat-element": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz",
- "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/repeat-string": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
- "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
- "dev": true,
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/repeating": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
- "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
- "dev": true,
- "dependencies": {
- "is-finite": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/requireindex": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz",
@@ -8591,13 +7449,6 @@
"node": ">=4"
}
},
- "node_modules/resolve-url": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
- "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
- "deprecated": "https://github.com/lydell/resolve-url#deprecated",
- "dev": true
- },
"node_modules/restore-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
@@ -8611,15 +7462,6 @@
"node": ">=8"
}
},
- "node_modules/ret": {
- "version": "0.1.15",
- "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
- "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
- "dev": true,
- "engines": {
- "node": ">=0.12"
- }
- },
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -8740,15 +7582,6 @@
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
},
- "node_modules/safe-regex": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
- "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
- "dev": true,
- "dependencies": {
- "ret": "~0.1.10"
- }
- },
"node_modules/safe-regex-test": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
@@ -8769,12 +7602,6 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
- "node_modules/sax": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
- "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
- "dev": true
- },
"node_modules/screenshot-ftw": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/screenshot-ftw/-/screenshot-ftw-1.0.5.tgz",
@@ -8975,21 +7802,6 @@
"node": ">= 0.4"
}
},
- "node_modules/set-value": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
- "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
- "dev": true,
- "dependencies": {
- "extend-shallow": "^2.0.1",
- "is-extendable": "^0.1.1",
- "is-plain-object": "^2.0.3",
- "split-string": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -9058,158 +7870,6 @@
"node": ">=6"
}
},
- "node_modules/snapdragon": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
- "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
- "dev": true,
- "dependencies": {
- "base": "^0.11.1",
- "debug": "^2.2.0",
- "define-property": "^0.2.5",
- "extend-shallow": "^2.0.1",
- "map-cache": "^0.2.2",
- "source-map": "^0.5.6",
- "source-map-resolve": "^0.5.0",
- "use": "^3.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-node": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
- "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
- "dev": true,
- "dependencies": {
- "define-property": "^1.0.0",
- "isobject": "^3.0.0",
- "snapdragon-util": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-node/node_modules/define-property": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
- "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
- "dev": true,
- "dependencies": {
- "is-descriptor": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-node/node_modules/is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-node/node_modules/is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "dependencies": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-util": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
- "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^3.2.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-util/node_modules/kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/snapdragon/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
- "dev": true
- },
- "node_modules/source-map": {
- "version": "0.5.7",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
- "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/source-map-resolve": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
- "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
- "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated",
- "dev": true,
- "dependencies": {
- "atob": "^2.1.2",
- "decode-uri-component": "^0.2.0",
- "resolve-url": "^0.2.1",
- "source-map-url": "^0.4.0",
- "urix": "^0.1.0"
- }
- },
- "node_modules/source-map-url": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
- "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==",
- "deprecated": "See https://github.com/lydell/source-map-url#deprecated",
- "dev": true
- },
"node_modules/spdx-correct": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
@@ -9242,41 +7902,13 @@
"integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==",
"dev": true
},
- "node_modules/split-string": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
- "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
- "dev": true,
- "dependencies": {
- "extend-shallow": "^3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/split-string/node_modules/extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "dependencies": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/split-string/node_modules/is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "node_modules/split2": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz",
+ "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==",
"dev": true,
"dependencies": {
- "is-plain-object": "^2.0.4"
- },
- "engines": {
- "node": ">=0.10.0"
+ "through2": "^2.0.2"
}
},
"node_modules/sprintf-js": {
@@ -9285,19 +7917,6 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
- "node_modules/static-extend": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
- "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
- "dev": true,
- "dependencies": {
- "define-property": "^0.2.5",
- "object-copy": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
@@ -9307,6 +7926,12 @@
"node": ">= 0.6"
}
},
+ "node_modules/stream-shift": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
+ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
+ "dev": true
+ },
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -9387,18 +8012,6 @@
"node": ">=8"
}
},
- "node_modules/strip-bom": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
- "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
- "dev": true,
- "dependencies": {
- "is-utf8": "^0.2.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
@@ -9484,6 +8097,16 @@
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
+ "node_modules/through2": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+ "dev": true,
+ "dependencies": {
+ "readable-stream": "~2.3.6",
+ "xtend": "~4.0.1"
+ }
+ },
"node_modules/titleize": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
@@ -9517,45 +8140,6 @@
"node": ">=4"
}
},
- "node_modules/to-object-path": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
- "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
- "dev": true,
- "dependencies": {
- "kind-of": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/to-object-path/node_modules/kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/to-regex": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
- "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
- "dev": true,
- "dependencies": {
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "regex-not": "^1.0.2",
- "safe-regex": "^1.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -9568,82 +8152,6 @@
"node": ">=8.0"
}
},
- "node_modules/to-regex/node_modules/define-property": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
- "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
- "dev": true,
- "dependencies": {
- "is-descriptor": "^1.0.2",
- "isobject": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/to-regex/node_modules/extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "dependencies": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/to-regex/node_modules/is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/to-regex/node_modules/is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/to-regex/node_modules/is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "dependencies": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/to-regex/node_modules/is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "dependencies": {
- "is-plain-object": "^2.0.4"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -10004,21 +8512,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/union-value": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
- "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
- "dev": true,
- "dependencies": {
- "arr-union": "^3.1.0",
- "get-value": "^2.0.6",
- "is-extendable": "^0.1.1",
- "set-value": "^2.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -10028,54 +8521,6 @@
"node": ">= 0.8"
}
},
- "node_modules/unset-value": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
- "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
- "dev": true,
- "dependencies": {
- "has-value": "^0.3.1",
- "isobject": "^3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/unset-value/node_modules/has-value": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
- "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
- "dev": true,
- "dependencies": {
- "get-value": "^2.0.3",
- "has-values": "^0.1.4",
- "isobject": "^2.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/unset-value/node_modules/has-value/node_modules/isobject": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
- "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
- "dev": true,
- "dependencies": {
- "isarray": "1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/unset-value/node_modules/has-values": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
- "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/untildify": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
@@ -10085,16 +8530,6 @@
"node": ">=8"
}
},
- "node_modules/upath": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
- "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
- "dev": true,
- "engines": {
- "node": ">=4",
- "yarn": "*"
- }
- },
"node_modules/update-browserslist-db": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
@@ -10134,28 +8569,12 @@
"punycode": "^2.1.0"
}
},
- "node_modules/urix": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
- "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
- "deprecated": "Please see https://github.com/lydell/urix#deprecated",
- "dev": true
- },
"node_modules/url-join": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
"dev": true
},
- "node_modules/use": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
- "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -10313,26 +8732,13 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
- "node_modules/xml2js": {
- "version": "0.4.23",
- "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
- "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
- "dev": true,
- "dependencies": {
- "sax": ">=0.6.0",
- "xmlbuilder": "~11.0.0"
- },
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/xmlbuilder": {
- "version": "11.0.1",
- "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
- "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"dev": true,
"engines": {
- "node": ">=4.0"
+ "node": ">=0.4"
}
},
"node_modules/yallist": {
@@ -11464,9 +9870,9 @@
"dev": true
},
"@webgpu/types": {
- "version": "0.1.38",
- "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.38.tgz",
- "integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==",
+ "version": "0.1.40",
+ "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.40.tgz",
+ "integrity": "sha512-/BBkHLS6/eQjyWhY2H7Dx5DHcVrS2ICj9owvSRdgtQT6KcafLZA86tPze0xAOsd4FbsYKCUBUQyNi87q7gV7kw==",
"dev": true
},
"abbrev": {
@@ -11577,24 +9983,6 @@
"sprintf-js": "~1.0.2"
}
},
- "arr-diff": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
- "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
- "dev": true
- },
- "arr-flatten": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
- "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
- "dev": true
- },
- "arr-union": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
- "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
- "dev": true
- },
"array-buffer-byte-length": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
@@ -11642,12 +10030,6 @@
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true
},
- "array-unique": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
- "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
- "dev": true
- },
"array.prototype.findlastindex": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz",
@@ -11706,30 +10088,12 @@
"integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
"dev": true
},
- "assign-symbols": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
- "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
- "dev": true
- },
"async": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
"dev": true
},
- "async-each": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
- "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==",
- "dev": true
- },
- "atob": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
- "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
- "dev": true
- },
"available-typed-arrays": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
@@ -11759,61 +10123,6 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
- "base": {
- "version": "0.11.2",
- "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
- "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
- "dev": true,
- "requires": {
- "cache-base": "^1.0.1",
- "class-utils": "^0.3.5",
- "component-emitter": "^1.2.1",
- "define-property": "^1.0.0",
- "isobject": "^3.0.1",
- "mixin-deep": "^1.2.0",
- "pascalcase": "^0.1.1"
- },
- "dependencies": {
- "define-property": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
- "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
- "dev": true,
- "requires": {
- "is-descriptor": "^1.0.0"
- }
- },
- "is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "requires": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- }
- }
- }
- },
"bash-color": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/bash-color/-/bash-color-0.0.3.tgz",
@@ -11847,16 +10156,6 @@
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true
},
- "bindings": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
- "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
- "dev": true,
- "optional": true,
- "requires": {
- "file-uri-to-path": "1.0.0"
- }
- },
"body-parser": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
@@ -11964,23 +10263,6 @@
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"dev": true
},
- "cache-base": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
- "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
- "dev": true,
- "requires": {
- "collection-visit": "^1.0.0",
- "component-emitter": "^1.2.1",
- "get-value": "^2.0.6",
- "has-value": "^1.0.0",
- "isobject": "^3.0.1",
- "set-value": "^2.0.0",
- "to-object-path": "^0.3.0",
- "union-value": "^1.0.0",
- "unset-value": "^1.0.0"
- }
- },
"call-bind": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
@@ -12054,18 +10336,6 @@
"readdirp": "~3.6.0"
}
},
- "class-utils": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
- "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
- "dev": true,
- "requires": {
- "arr-union": "^3.1.0",
- "define-property": "^0.2.5",
- "isobject": "^3.0.0",
- "static-extend": "^0.1.1"
- }
- },
"cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
@@ -12081,16 +10351,6 @@
"integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==",
"dev": true
},
- "collection-visit": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
- "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
- "dev": true,
- "requires": {
- "map-visit": "^1.0.0",
- "object-visit": "^1.0.0"
- }
- },
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -12112,12 +10372,6 @@
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
"dev": true
},
- "component-emitter": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
- "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
- "dev": true
- },
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -12165,12 +10419,6 @@
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
"dev": true
},
- "copy-descriptor": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
- "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
- "dev": true
- },
"core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -12200,32 +10448,6 @@
"which": "^2.0.1"
}
},
- "csproj2ts": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/csproj2ts/-/csproj2ts-1.1.0.tgz",
- "integrity": "sha512-sk0RTT51t4lUNQ7UfZrqjQx7q4g0m3iwNA6mvyh7gLsgQYvwKzfdyoAgicC9GqJvkoIkU0UmndV9c7VZ8pJ45Q==",
- "dev": true,
- "requires": {
- "es6-promise": "^4.1.1",
- "lodash": "^4.17.4",
- "semver": "^5.4.1",
- "xml2js": "^0.4.19"
- },
- "dependencies": {
- "es6-promise": {
- "version": "4.2.8",
- "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
- "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
- "dev": true
- },
- "semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
- "dev": true
- }
- }
- },
"d": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
@@ -12275,12 +10497,6 @@
}
}
},
- "decode-uri-component": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
- "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
- "dev": true
- },
"deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -12404,15 +10620,6 @@
"object-keys": "^1.1.1"
}
},
- "define-property": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
- "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
- "dev": true,
- "requires": {
- "is-descriptor": "^0.1.0"
- }
- },
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -12431,21 +10638,6 @@
"integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
"dev": true
},
- "detect-indent": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
- "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=",
- "dev": true,
- "requires": {
- "repeating": "^2.0.0"
- }
- },
- "detect-newline": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz",
- "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=",
- "dev": true
- },
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -12470,6 +10662,18 @@
"esutils": "^2.0.2"
}
},
+ "duplexify": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
+ "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.0.0",
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0",
+ "stream-shift": "^1.0.0"
+ }
+ },
"duration": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/duration/-/duration-0.2.2.tgz",
@@ -12504,6 +10708,15 @@
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"dev": true
},
+ "end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dev": true,
+ "requires": {
+ "once": "^1.4.0"
+ }
+ },
"error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -12613,12 +10826,6 @@
"es6-symbol": "^3.1.1"
}
},
- "es6-promise": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-0.1.2.tgz",
- "integrity": "sha1-8RLCn+paCZhTn8tqL9IUQ9KPBfc=",
- "dev": true
- },
"es6-symbol": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
@@ -13061,38 +11268,6 @@
"integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
"dev": true
},
- "expand-brackets": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
- "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
- "dev": true,
- "requires": {
- "debug": "^2.3.3",
- "define-property": "^0.2.5",
- "extend-shallow": "^2.0.1",
- "posix-character-classes": "^0.1.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.1"
- },
- "dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "requires": {
- "ms": "2.0.0"
- }
- },
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
- "dev": true
- }
- }
- },
"expand-tilde": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
@@ -13208,15 +11383,6 @@
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"dev": true
},
- "extend-shallow": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
- "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
- "dev": true,
- "requires": {
- "is-extendable": "^0.1.0"
- }
- },
"external-editor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -13228,62 +11394,6 @@
"tmp": "^0.0.33"
}
},
- "extglob": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
- "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
- "dev": true,
- "requires": {
- "array-unique": "^0.3.2",
- "define-property": "^1.0.0",
- "expand-brackets": "^2.1.4",
- "extend-shallow": "^2.0.1",
- "fragment-cache": "^0.2.1",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.1"
- },
- "dependencies": {
- "define-property": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
- "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
- "dev": true,
- "requires": {
- "is-descriptor": "^1.0.0"
- }
- },
- "is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "requires": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- }
- }
- }
- },
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -13354,13 +11464,6 @@
"integrity": "sha1-peeo/7+kk7Q7kju9TKiaU7Y7YSs=",
"dev": true
},
- "file-uri-to-path": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
- "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
- "dev": true,
- "optional": true
- },
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -13510,15 +11613,6 @@
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"dev": true
},
- "fragment-cache": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
- "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
- "dev": true,
- "requires": {
- "map-cache": "^0.2.2"
- }
- },
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@@ -13608,12 +11702,6 @@
"get-intrinsic": "^1.1.1"
}
},
- "get-value": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
- "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
- "dev": true
- },
"getobject": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz",
@@ -13730,12 +11818,6 @@
"get-intrinsic": "^1.1.3"
}
},
- "graceful-fs": {
- "version": "4.2.9",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz",
- "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
- "dev": true
- },
"graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -13822,6 +11904,26 @@
}
}
},
+ "grunt-concurrent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/grunt-concurrent/-/grunt-concurrent-3.0.0.tgz",
+ "integrity": "sha512-AgXtjUJESHEGeGX8neL3nmXBTHSj1QC48ABQ3ng2/vjuSBpDD8gKcVHSlXP71pFkIR8TQHf+eomOx6OSYSgfrA==",
+ "dev": true,
+ "requires": {
+ "arrify": "^2.0.1",
+ "async": "^3.1.0",
+ "indent-string": "^4.0.0",
+ "pad-stream": "^2.0.0"
+ },
+ "dependencies": {
+ "arrify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
+ "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
+ "dev": true
+ }
+ }
+ },
"grunt-contrib-clean": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-2.0.1.tgz",
@@ -14027,279 +12129,6 @@
"hooker": "^0.2.3"
}
},
- "grunt-ts": {
- "version": "6.0.0-beta.22",
- "resolved": "https://registry.npmjs.org/grunt-ts/-/grunt-ts-6.0.0-beta.22.tgz",
- "integrity": "sha512-g9e+ZImQ7W38dfpwhp0+GUltXWidy3YGPfIA/IyGL5HMv6wmVmMMoSgscI5swhs2HSPf8yAvXAAJbwrouijoRg==",
- "dev": true,
- "requires": {
- "chokidar": "^2.0.4",
- "csproj2ts": "^1.1.0",
- "detect-indent": "^4.0.0",
- "detect-newline": "^2.1.0",
- "es6-promise": "~0.1.1",
- "jsmin2": "^1.2.1",
- "lodash": "~4.17.10",
- "ncp": "0.5.1",
- "rimraf": "2.2.6",
- "semver": "^5.3.0",
- "strip-bom": "^2.0.0"
- },
- "dependencies": {
- "anymatch": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
- "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
- "dev": true,
- "requires": {
- "micromatch": "^3.1.4",
- "normalize-path": "^2.1.1"
- },
- "dependencies": {
- "normalize-path": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
- "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
- "dev": true,
- "requires": {
- "remove-trailing-separator": "^1.0.1"
- }
- }
- }
- },
- "binary-extensions": {
- "version": "1.13.1",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
- "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
- "dev": true
- },
- "braces": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
- "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
- "dev": true,
- "requires": {
- "arr-flatten": "^1.1.0",
- "array-unique": "^0.3.2",
- "extend-shallow": "^2.0.1",
- "fill-range": "^4.0.0",
- "isobject": "^3.0.1",
- "repeat-element": "^1.1.2",
- "snapdragon": "^0.8.1",
- "snapdragon-node": "^2.0.1",
- "split-string": "^3.0.2",
- "to-regex": "^3.0.1"
- }
- },
- "chokidar": {
- "version": "2.1.8",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
- "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==",
- "dev": true,
- "requires": {
- "anymatch": "^2.0.0",
- "async-each": "^1.0.1",
- "braces": "^2.3.2",
- "fsevents": "^1.2.7",
- "glob-parent": "^3.1.0",
- "inherits": "^2.0.3",
- "is-binary-path": "^1.0.0",
- "is-glob": "^4.0.0",
- "normalize-path": "^3.0.0",
- "path-is-absolute": "^1.0.0",
- "readdirp": "^2.2.1",
- "upath": "^1.1.1"
- }
- },
- "define-property": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
- "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
- "dev": true,
- "requires": {
- "is-descriptor": "^1.0.2",
- "isobject": "^3.0.1"
- }
- },
- "fill-range": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
- "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
- "dev": true,
- "requires": {
- "extend-shallow": "^2.0.1",
- "is-number": "^3.0.0",
- "repeat-string": "^1.6.1",
- "to-regex-range": "^2.1.0"
- }
- },
- "fsevents": {
- "version": "1.2.13",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
- "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
- "dev": true,
- "optional": true,
- "requires": {
- "bindings": "^1.5.0",
- "nan": "^2.12.1"
- }
- },
- "glob-parent": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
- "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
- "dev": true,
- "requires": {
- "is-glob": "^3.1.0",
- "path-dirname": "^1.0.0"
- },
- "dependencies": {
- "is-glob": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
- "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
- "dev": true,
- "requires": {
- "is-extglob": "^2.1.0"
- }
- }
- }
- },
- "is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-binary-path": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
- "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
- "dev": true,
- "requires": {
- "binary-extensions": "^1.0.0"
- }
- },
- "is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "requires": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- }
- },
- "is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "requires": {
- "is-plain-object": "^2.0.4"
- }
- },
- "is-number": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
- "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
- "dev": true,
- "requires": {
- "kind-of": "^3.0.2"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
- "micromatch": {
- "version": "3.1.10",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
- "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
- "dev": true,
- "requires": {
- "arr-diff": "^4.0.0",
- "array-unique": "^0.3.2",
- "braces": "^2.3.1",
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "extglob": "^2.0.4",
- "fragment-cache": "^0.2.1",
- "kind-of": "^6.0.2",
- "nanomatch": "^1.2.9",
- "object.pick": "^1.3.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.2"
- },
- "dependencies": {
- "extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "requires": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- }
- }
- }
- },
- "readdirp": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz",
- "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.11",
- "micromatch": "^3.1.10",
- "readable-stream": "^2.0.2"
- }
- },
- "rimraf": {
- "version": "2.2.6",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.6.tgz",
- "integrity": "sha1-xZWXVpsU2VatKcrMQr3d9fDqT0w=",
- "dev": true
- },
- "semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
- "dev": true
- },
- "to-regex-range": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
- "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
- "dev": true,
- "requires": {
- "is-number": "^3.0.0",
- "repeat-string": "^1.6.1"
- }
- }
- }
- },
"gts": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/gts/-/gts-5.2.0.tgz",
@@ -14655,58 +12484,6 @@
"has-symbols": "^1.0.2"
}
},
- "has-value": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
- "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
- "dev": true,
- "requires": {
- "get-value": "^2.0.6",
- "has-values": "^1.0.0",
- "isobject": "^3.0.0"
- }
- },
- "has-values": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
- "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
- "dev": true,
- "requires": {
- "is-number": "^3.0.0",
- "kind-of": "^4.0.0"
- },
- "dependencies": {
- "is-number": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
- "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
- "dev": true,
- "requires": {
- "kind-of": "^3.0.2"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
- "kind-of": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
- "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
"hasown": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
@@ -15029,26 +12806,6 @@
"is-windows": "^1.0.1"
}
},
- "is-accessor-descriptor": {
- "version": "0.1.6",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
- "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
- "dev": true,
- "requires": {
- "kind-of": "^3.0.2"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
"is-array-buffer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
@@ -15094,12 +12851,6 @@
"has-tostringtag": "^1.0.0"
}
},
- "is-buffer": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
- "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
- "dev": true
- },
"is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -15115,26 +12866,6 @@
"hasown": "^2.0.0"
}
},
- "is-data-descriptor": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
- "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
- "dev": true,
- "requires": {
- "kind-of": "^3.0.2"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
"is-date-object": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
@@ -15144,49 +12875,18 @@
"has-tostringtag": "^1.0.0"
}
},
- "is-descriptor": {
- "version": "0.1.6",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
- "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
- "dev": true,
- "requires": {
- "is-accessor-descriptor": "^0.1.6",
- "is-data-descriptor": "^0.1.4",
- "kind-of": "^5.0.0"
- },
- "dependencies": {
- "kind-of": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
- "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
- "dev": true
- }
- }
- },
"is-docker": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
"integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
"dev": true
},
- "is-extendable": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
- "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
- "dev": true
- },
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
},
- "is-finite": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz",
- "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==",
- "dev": true
- },
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -15323,12 +13023,6 @@
"unc-path-regex": "^0.1.2"
}
},
- "is-utf8": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
- "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
- "dev": true
- },
"is-weakref": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
@@ -15401,12 +13095,6 @@
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"dev": true
},
- "jsmin2": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/jsmin2/-/jsmin2-1.2.1.tgz",
- "integrity": "sha1-iPvi+/dfCpH2YCD9mBzWk/S/5X4=",
- "dev": true
- },
"json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
@@ -15570,15 +13258,6 @@
"integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
"dev": true
},
- "map-visit": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
- "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
- "dev": true,
- "requires": {
- "object-visit": "^1.0.0"
- }
- },
"marked": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
@@ -15712,27 +13391,6 @@
"kind-of": "^6.0.3"
}
},
- "mixin-deep": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
- "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
- "dev": true,
- "requires": {
- "for-in": "^1.0.2",
- "is-extendable": "^1.0.1"
- },
- "dependencies": {
- "is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "requires": {
- "is-plain-object": "^2.0.4"
- }
- }
- }
- },
"morgan": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
@@ -15781,92 +13439,6 @@
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
"dev": true
},
- "nan": {
- "version": "2.15.0",
- "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
- "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
- "dev": true,
- "optional": true
- },
- "nanomatch": {
- "version": "1.2.13",
- "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
- "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
- "dev": true,
- "requires": {
- "arr-diff": "^4.0.0",
- "array-unique": "^0.3.2",
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "fragment-cache": "^0.2.1",
- "is-windows": "^1.0.2",
- "kind-of": "^6.0.2",
- "object.pick": "^1.3.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.1"
- },
- "dependencies": {
- "define-property": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
- "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
- "dev": true,
- "requires": {
- "is-descriptor": "^1.0.2",
- "isobject": "^3.0.1"
- }
- },
- "extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "requires": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- }
- },
- "is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "requires": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- }
- },
- "is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "requires": {
- "is-plain-object": "^2.0.4"
- }
- }
- }
- },
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -15879,12 +13451,6 @@
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
"dev": true
},
- "ncp": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.5.1.tgz",
- "integrity": "sha1-dDmFMW49tFkoG1hxaehFc1oFQ58=",
- "dev": true
- },
"negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -15950,28 +13516,6 @@
"path-key": "^3.0.0"
}
},
- "object-copy": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
- "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
- "dev": true,
- "requires": {
- "copy-descriptor": "^0.1.0",
- "define-property": "^0.2.5",
- "kind-of": "^3.0.3"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
"object-inspect": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
@@ -15984,15 +13528,6 @@
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true
},
- "object-visit": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
- "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
- "dev": true,
- "requires": {
- "isobject": "^3.0.0"
- }
- },
"object.assign": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
@@ -16175,6 +13710,17 @@
"p-limit": "^3.0.2"
}
},
+ "pad-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pad-stream/-/pad-stream-2.0.0.tgz",
+ "integrity": "sha512-3QeQw19K48BQzUGZ9dEf/slX5Jbfy5ZeBTma2XICketO7kFNK7omF00riVcecOKN+DSiJZcK2em1eYKaVOeXKg==",
+ "dev": true,
+ "requires": {
+ "pumpify": "^1.3.3",
+ "split2": "^2.1.1",
+ "through2": "^2.0.0"
+ }
+ },
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -16219,18 +13765,6 @@
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"dev": true
},
- "pascalcase": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
- "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
- "dev": true
- },
- "path-dirname": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
- "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
- "dev": true
- },
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -16352,12 +13886,6 @@
}
}
},
- "posix-character-classes": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
- "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
- "dev": true
- },
"prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -16395,6 +13923,27 @@
"ipaddr.js": "1.9.1"
}
},
+ "pump": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
+ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "pumpify": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
+ "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
+ "dev": true,
+ "requires": {
+ "duplexify": "^3.6.0",
+ "inherits": "^2.0.3",
+ "pump": "^2.0.0"
+ }
+ },
"punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -16589,37 +14138,6 @@
"strip-indent": "^3.0.0"
}
},
- "regex-not": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
- "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
- "dev": true,
- "requires": {
- "extend-shallow": "^3.0.2",
- "safe-regex": "^1.1.0"
- },
- "dependencies": {
- "extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "requires": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- }
- },
- "is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "requires": {
- "is-plain-object": "^2.0.4"
- }
- }
- }
- },
"regexp.prototype.flags": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
@@ -16637,33 +14155,6 @@
"integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
"dev": true
},
- "remove-trailing-separator": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
- "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
- "dev": true
- },
- "repeat-element": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz",
- "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==",
- "dev": true
- },
- "repeat-string": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
- "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
- "dev": true
- },
- "repeating": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
- "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
- "dev": true,
- "requires": {
- "is-finite": "^1.0.0"
- }
- },
"requireindex": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz",
@@ -16703,12 +14194,6 @@
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
- "resolve-url": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
- "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
- "dev": true
- },
"restore-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
@@ -16719,12 +14204,6 @@
"signal-exit": "^3.0.2"
}
},
- "ret": {
- "version": "0.1.15",
- "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
- "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
- "dev": true
- },
"reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -16807,15 +14286,6 @@
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
},
- "safe-regex": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
- "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
- "dev": true,
- "requires": {
- "ret": "~0.1.10"
- }
- },
"safe-regex-test": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
@@ -16833,12 +14303,6 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
- "sax": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
- "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
- "dev": true
- },
"screenshot-ftw": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/screenshot-ftw/-/screenshot-ftw-1.0.5.tgz",
@@ -17015,18 +14479,6 @@
"has-property-descriptors": "^1.0.0"
}
},
- "set-value": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
- "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
- "dev": true,
- "requires": {
- "extend-shallow": "^2.0.1",
- "is-extendable": "^0.1.1",
- "is-plain-object": "^2.0.3",
- "split-string": "^3.0.1"
- }
- },
"setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -17083,135 +14535,6 @@
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
"dev": true
},
- "snapdragon": {
- "version": "0.8.2",
- "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
- "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
- "dev": true,
- "requires": {
- "base": "^0.11.1",
- "debug": "^2.2.0",
- "define-property": "^0.2.5",
- "extend-shallow": "^2.0.1",
- "map-cache": "^0.2.2",
- "source-map": "^0.5.6",
- "source-map-resolve": "^0.5.0",
- "use": "^3.1.0"
- },
- "dependencies": {
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dev": true,
- "requires": {
- "ms": "2.0.0"
- }
- },
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
- "dev": true
- }
- }
- },
- "snapdragon-node": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
- "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
- "dev": true,
- "requires": {
- "define-property": "^1.0.0",
- "isobject": "^3.0.0",
- "snapdragon-util": "^3.0.1"
- },
- "dependencies": {
- "define-property": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
- "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
- "dev": true,
- "requires": {
- "is-descriptor": "^1.0.0"
- }
- },
- "is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "requires": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- }
- }
- }
- },
- "snapdragon-util": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
- "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
- "dev": true,
- "requires": {
- "kind-of": "^3.2.0"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
- "source-map": {
- "version": "0.5.7",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
- "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
- "dev": true
- },
- "source-map-resolve": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
- "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
- "dev": true,
- "requires": {
- "atob": "^2.1.2",
- "decode-uri-component": "^0.2.0",
- "resolve-url": "^0.2.1",
- "source-map-url": "^0.4.0",
- "urix": "^0.1.0"
- }
- },
- "source-map-url": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
- "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==",
- "dev": true
- },
"spdx-correct": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
@@ -17244,34 +14567,13 @@
"integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==",
"dev": true
},
- "split-string": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
- "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+ "split2": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz",
+ "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==",
"dev": true,
"requires": {
- "extend-shallow": "^3.0.0"
- },
- "dependencies": {
- "extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "requires": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- }
- },
- "is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "requires": {
- "is-plain-object": "^2.0.4"
- }
- }
+ "through2": "^2.0.2"
}
},
"sprintf-js": {
@@ -17280,22 +14582,18 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
- "static-extend": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
- "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
- "dev": true,
- "requires": {
- "define-property": "^0.2.5",
- "object-copy": "^0.1.0"
- }
- },
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
"dev": true
},
+ "stream-shift": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
+ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
+ "dev": true
+ },
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -17358,15 +14656,6 @@
"ansi-regex": "^5.0.1"
}
},
- "strip-bom": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
- "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
- "dev": true,
- "requires": {
- "is-utf8": "^0.2.0"
- }
- },
"strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
@@ -17425,6 +14714,16 @@
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
+ "through2": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
+ "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
+ "dev": true,
+ "requires": {
+ "readable-stream": "~2.3.6",
+ "xtend": "~4.0.1"
+ }
+ },
"titleize": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
@@ -17446,98 +14745,6 @@
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
"dev": true
},
- "to-object-path": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
- "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
- "dev": true,
- "requires": {
- "kind-of": "^3.0.2"
- },
- "dependencies": {
- "kind-of": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
- "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
- "dev": true,
- "requires": {
- "is-buffer": "^1.1.5"
- }
- }
- }
- },
- "to-regex": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
- "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
- "dev": true,
- "requires": {
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "regex-not": "^1.0.2",
- "safe-regex": "^1.1.0"
- },
- "dependencies": {
- "define-property": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
- "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
- "dev": true,
- "requires": {
- "is-descriptor": "^1.0.2",
- "isobject": "^3.0.1"
- }
- },
- "extend-shallow": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
- "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
- "dev": true,
- "requires": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- }
- },
- "is-accessor-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
- "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-data-descriptor": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
- "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
- "dev": true,
- "requires": {
- "kind-of": "^6.0.0"
- }
- },
- "is-descriptor": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
- "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
- "dev": true,
- "requires": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- }
- },
- "is-extendable": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
- "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
- "dev": true,
- "requires": {
- "is-plain-object": "^2.0.4"
- }
- }
- }
- },
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -17806,76 +15013,18 @@
"qs": "^6.4.0"
}
},
- "union-value": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
- "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
- "dev": true,
- "requires": {
- "arr-union": "^3.1.0",
- "get-value": "^2.0.6",
- "is-extendable": "^0.1.1",
- "set-value": "^2.0.1"
- }
- },
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"dev": true
},
- "unset-value": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
- "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
- "dev": true,
- "requires": {
- "has-value": "^0.3.1",
- "isobject": "^3.0.0"
- },
- "dependencies": {
- "has-value": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
- "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
- "dev": true,
- "requires": {
- "get-value": "^2.0.3",
- "has-values": "^0.1.4",
- "isobject": "^2.0.0"
- },
- "dependencies": {
- "isobject": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
- "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
- "dev": true,
- "requires": {
- "isarray": "1.0.0"
- }
- }
- }
- },
- "has-values": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
- "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
- "dev": true
- }
- }
- },
"untildify": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
"integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
"dev": true
},
- "upath": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
- "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
- "dev": true
- },
"update-browserslist-db": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
@@ -17895,24 +15044,12 @@
"punycode": "^2.1.0"
}
},
- "urix": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
- "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
- "dev": true
- },
"url-join": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
"dev": true
},
- "use": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
- "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
- "dev": true
- },
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -18039,20 +15176,10 @@
"signal-exit": "^3.0.7"
}
},
- "xml2js": {
- "version": "0.4.23",
- "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
- "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
- "dev": true,
- "requires": {
- "sax": ">=0.6.0",
- "xmlbuilder": "~11.0.0"
- }
- },
- "xmlbuilder": {
- "version": "11.0.1",
- "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
- "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"dev": true
},
"yallist": {
diff --git a/dom/webgpu/tests/cts/checkout/package.json b/dom/webgpu/tests/cts/checkout/package.json
index 21f74065af..8f2718ab37 100644
--- a/dom/webgpu/tests/cts/checkout/package.json
+++ b/dom/webgpu/tests/cts/checkout/package.json
@@ -3,15 +3,18 @@
"version": "0.1.0",
"description": "WebGPU Conformance Test Suite",
"scripts": {
- "test": "grunt pre",
- "check": "grunt check",
+ "test": "grunt all",
+ "all": "grunt all",
"standalone": "grunt standalone",
"wpt": "grunt wpt",
- "fix": "grunt fix",
+ "node": "grunt node",
+ "checks": "grunt checks",
"unittest": "grunt unittest",
+ "typecheck": "grunt typecheck",
+ "fix": "grunt fix",
"gen_wpt_cts_html": "node tools/gen_wpt_cts_html",
"gen_cache": "node tools/gen_cache",
- "tsdoc": "grunt run:tsdoc",
+ "tsdoc": "grunt tsdoc",
"start": "node tools/dev_server",
"dev": "node tools/dev_server"
},
@@ -46,7 +49,7 @@
"@types/serve-index": "^1.9.3",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
- "@webgpu/types": "^0.1.38",
+ "@webgpu/types": "^0.1.40",
"ansi-colors": "4.1.3",
"babel-plugin-add-header-comment": "^1.0.3",
"babel-plugin-const-enum": "^1.2.0",
@@ -59,11 +62,11 @@
"express": "^4.18.2",
"grunt": "^1.6.1",
"grunt-cli": "^1.4.3",
+ "grunt-concurrent": "^3.0.0",
"grunt-contrib-clean": "^2.0.1",
"grunt-contrib-copy": "^1.0.0",
"grunt-run": "^0.8.1",
"grunt-timer": "^0.6.0",
- "grunt-ts": "^6.0.0-beta.22",
"gts": "^5.2.0",
"http-server": "^14.1.1",
"morgan": "^1.10.0",
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;
+}
diff --git a/dom/webgpu/tests/cts/checkout/tools/gen_listings b/dom/webgpu/tests/cts/checkout/tools/gen_listings
deleted file mode 100755
index 6c25622423..0000000000
--- a/dom/webgpu/tests/cts/checkout/tools/gen_listings
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env node
-
-// Crawl a suite directory (e.g. src/webgpu/) to generate a listing.js containing
-// the listing of test files in the suite.
-
-require('../src/common/tools/setup-ts-in-node.js');
-require('../src/common/tools/gen_listings.ts');
diff --git a/dom/webgpu/tests/cts/checkout/tools/gen_listings_and_webworkers b/dom/webgpu/tests/cts/checkout/tools/gen_listings_and_webworkers
new file mode 100644
index 0000000000..285df3657d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/tools/gen_listings_and_webworkers
@@ -0,0 +1,8 @@
+#!/usr/bin/env node
+
+// Crawl a suite directory (e.g. src/webgpu/) to generate a listing.js containing
+// the listing of test files in the suite, and some generated test files
+// (like suite/serviceworker/**/*.spec.js).
+
+require('../src/common/tools/setup-ts-in-node.js');
+require('../src/common/tools/gen_listings_and_webworkers.ts');
diff --git a/dom/webgpu/tests/cts/checkout/tools/gen_version b/dom/webgpu/tests/cts/checkout/tools/gen_version
index 53128ca2a0..2c17db1454 100755
--- a/dom/webgpu/tests/cts/checkout/tools/gen_version
+++ b/dom/webgpu/tests/cts/checkout/tools/gen_version
@@ -1,6 +1,6 @@
#!/usr/bin/env node
-// Get the current git hash, and save (overwrite) it into out/framework/version.js
+// Get the current git hash, and save (overwrite) it into gen/.../version.js
// so it can be read when running inside the browser.
/* eslint-disable no-console */
@@ -16,18 +16,13 @@ if (!fs.existsSync(myself)) {
const { version } = require('../src/common/tools/version.ts');
-fs.mkdirSync('./out/common/internal', { recursive: true });
-// Overwrite the version.js generated by TypeScript compilation.
+fs.mkdirSync('./gen/common/internal', { recursive: true });
+// This will be copied into the various other build directories.
fs.writeFileSync(
- './out/common/internal/version.js',
+ './gen/common/internal/version.js',
`\
// AUTO-GENERATED - DO NOT EDIT. See ${myself}.
export const version = '${version}';
`
);
-
-// Since the generated version.js was overwritten, its source map is no longer relevant.
-try {
- fs.unlinkSync('./out/common/internal/version.js.map');
-} catch (ex) { }
diff --git a/dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_chunked2sec.json b/dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_chunked2sec.json
index 1d13e85c58..123a06e9cd 100644
--- a/dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_chunked2sec.json
+++ b/dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_chunked2sec.json
@@ -1,6 +1,7 @@
{
"suite": "webgpu",
"out": "../out-wpt/cts-chunked2sec.https.html",
+ "outVariantList": "../gen/webgpu_variant_list_chunked2sec.json",
"template": "../src/common/templates/cts.https.html",
"maxChunkTimeMS": 2000
}
diff --git a/dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_unchunked.json b/dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_unchunked.json
index ffe06d5633..9e8dfe229d 100644
--- a/dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_unchunked.json
+++ b/dom/webgpu/tests/cts/checkout/tools/gen_wpt_cfg_unchunked.json
@@ -1,5 +1,6 @@
{
"suite": "webgpu",
"out": "../out-wpt/cts.https.html",
+ "outVariantList": "../gen/webgpu_variant_list.json",
"template": "../src/common/templates/cts.https.html"
}
diff --git a/dom/webgpu/tests/cts/checkout/tools/server b/dom/webgpu/tests/cts/checkout/tools/server
new file mode 100644
index 0000000000..01e5a8a0c8
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/tools/server
@@ -0,0 +1,6 @@
+#!/usr/bin/env node
+
+// Launch a server that runs tests server-side on demand.
+
+require('../src/common/tools/setup-ts-in-node.js');
+require('../src/common/runtime/server.ts');
diff --git a/dom/webgpu/tests/cts/checkout_commit.txt b/dom/webgpu/tests/cts/checkout_commit.txt
index 1f42bc3256..723bd3f878 100644
--- a/dom/webgpu/tests/cts/checkout_commit.txt
+++ b/dom/webgpu/tests/cts/checkout_commit.txt
@@ -1 +1 @@
-41f89e77b67e6b66cb017be4e00235a0a9429ca7
+5c8510ec0d47180d1cd4dd92790b5a69335e162c
diff --git a/dom/webgpu/tests/cts/vendor/Cargo.lock b/dom/webgpu/tests/cts/vendor/Cargo.lock
index ea51837ca5..c1f2757393 100644
--- a/dom/webgpu/tests/cts/vendor/Cargo.lock
+++ b/dom/webgpu/tests/cts/vendor/Cargo.lock
@@ -30,9 +30,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
-version = "0.7.20"
+version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
@@ -125,6 +125,26 @@ dependencies = [
]
[[package]]
+name = "const_format"
+version = "0.2.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673"
+dependencies = [
+ "const_format_proc_macros",
+]
+
+[[package]]
+name = "const_format_proc_macros"
+version = "0.2.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
name = "crossbeam"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -366,6 +386,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
[[package]]
+name = "itertools"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+dependencies = [
+ "either",
+]
+
+[[package]]
name = "jwalk"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -449,6 +478,12 @@ dependencies = [
]
[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
name = "miniz_oxide"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -458,6 +493,16 @@ dependencies = [
]
[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
name = "num_cpus"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -495,6 +540,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
+name = "pori"
+version = "0.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a63d338dec139f56dacc692ca63ad35a6be6a797442479b55acd611d79e906"
+dependencies = [
+ "nom",
+]
+
+[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -560,9 +614,21 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.7.1"
+version = "1.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
+checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629"
dependencies = [
"aho-corasick",
"memchr",
@@ -571,9 +637,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
-version = "0.6.28"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
+checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "rustc-demangle"
@@ -740,6 +806,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
+name = "unicode-xid"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
+
+[[package]]
name = "vendor-webgpu-cts"
version = "0.1.0"
dependencies = [
@@ -748,12 +820,14 @@ dependencies = [
"dunce",
"env_logger",
"format",
+ "itertools",
"lets_find_up",
"log",
"miette",
"regex",
"shell-words",
"thiserror",
+ "wax",
"which",
]
@@ -765,12 +839,11 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
-version = "2.3.2"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
- "winapi",
"winapi-util",
]
@@ -781,6 +854,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
+name = "wax"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d12a78aa0bab22d2f26ed1a96df7ab58e8a93506a3e20adb47c51a93b4e1357"
+dependencies = [
+ "const_format",
+ "itertools",
+ "nom",
+ "pori",
+ "regex",
+ "thiserror",
+ "walkdir",
+]
+
+[[package]]
name = "which"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/dom/webgpu/tests/cts/vendor/Cargo.toml b/dom/webgpu/tests/cts/vendor/Cargo.toml
index d7721d1931..e90a947bad 100644
--- a/dom/webgpu/tests/cts/vendor/Cargo.toml
+++ b/dom/webgpu/tests/cts/vendor/Cargo.toml
@@ -9,12 +9,14 @@ dircpy = "0.3.14"
dunce = "1.0.3"
env_logger = "0.10.0"
format = "0.2.4"
+itertools = "0.11.0"
lets_find_up = "0.0.3"
log = "0.4.17"
miette = { version = "5.5.0", features = ["fancy"] }
regex = "1.7.1"
shell-words = "1.1.0"
thiserror = "1.0.38"
+wax = "0.6.0"
which = "4.4.0"
[workspace]
diff --git a/dom/webgpu/tests/cts/vendor/src/fs.rs b/dom/webgpu/tests/cts/vendor/src/fs.rs
index 31697f9758..3062f27cdb 100644
--- a/dom/webgpu/tests/cts/vendor/src/fs.rs
+++ b/dom/webgpu/tests/cts/vendor/src/fs.rs
@@ -245,15 +245,6 @@ impl Display for Child<'_> {
}
}
-pub(crate) fn existing_file<P>(path: P) -> P
-where
- P: AsRef<Path>,
-{
- let p = path.as_ref();
- assert!(p.is_file(), "{p:?} does not exist as a file");
- path
-}
-
pub(crate) fn copy_dir<P, Q>(source: P, dest: Q) -> miette::Result<()>
where
P: Display + AsRef<Path>,
@@ -297,6 +288,30 @@ where
})
}
+pub(crate) fn rename<P1, P2>(from: P1, to: P2) -> miette::Result<()>
+where
+ P1: AsRef<Path>,
+ P2: AsRef<Path>,
+{
+ fs::rename(&from, &to).into_diagnostic().wrap_err_with(|| {
+ format!(
+ "failed to rename {} to {}",
+ from.as_ref().display(),
+ to.as_ref().display()
+ )
+ })
+}
+
+pub(crate) fn try_exists<P>(path: P) -> miette::Result<bool>
+where
+ P: AsRef<Path>,
+{
+ let path = path.as_ref();
+ path.try_exists()
+ .into_diagnostic()
+ .wrap_err_with(|| format!("failed to check if path exists: {}", path.display()))
+}
+
pub(crate) fn create_dir_all<P>(path: P) -> miette::Result<()>
where
P: AsRef<Path>,
diff --git a/dom/webgpu/tests/cts/vendor/src/main.rs b/dom/webgpu/tests/cts/vendor/src/main.rs
index 750b65c62e..1171c90b3a 100644
--- a/dom/webgpu/tests/cts/vendor/src/main.rs
+++ b/dom/webgpu/tests/cts/vendor/src/main.rs
@@ -6,12 +6,13 @@ use std::{
};
use clap::Parser;
+use itertools::Itertools;
use lets_find_up::{find_up_with, FindUpKind, FindUpOptions};
use miette::{bail, ensure, miette, Context, Diagnostic, IntoDiagnostic, Report, SourceSpan};
use regex::Regex;
use crate::{
- fs::{copy_dir, create_dir_all, existing_file, remove_file, FileRoot},
+ fs::{copy_dir, create_dir_all, remove_file, FileRoot},
path::join_path,
process::{which, EasyCommand},
};
@@ -277,13 +278,6 @@ fn run(args: CliArgs) -> miette::Result<()> {
})?;
let cts_https_html_path = out_wpt_dir.child("cts.https.html");
- log::info!("refining the output of {cts_https_html_path} with `npm run gen_wpt_cts_html …`…");
- EasyCommand::new(&npm_bin, |cmd| {
- cmd.args(["run", "gen_wpt_cts_html"]).arg(existing_file(
- &cts_ckt.child("tools/gen_wpt_cfg_unchunked.json"),
- ))
- })
- .spawn()?;
{
let extra_cts_https_html_path = out_wpt_dir.child("cts-chunked2sec.https.html");
@@ -551,6 +545,45 @@ fn run(args: CliArgs) -> miette::Result<()> {
log::info!(" …removing {cts_https_html_path}, now that it's been divided up…");
remove_file(&cts_https_html_path)?;
+ log::info!("moving ready-to-go WPT test files into `cts`…");
+
+ let webgpu_dir = out_wpt_dir.child("webgpu");
+ let ready_to_go_tests = wax::Glob::new("**/*.{html,{any,sub,worker}.js}")
+ .unwrap()
+ .walk(&webgpu_dir)
+ .map_ok(|entry| webgpu_dir.child(entry.into_path()))
+ .collect::<Result<Vec<_>, _>>()
+ .map_err(Report::msg)
+ .wrap_err_with(|| {
+ format!("failed to walk {webgpu_dir} for ready-to-go WPT test files")
+ })?;
+
+ log::trace!(" …will move the following: {ready_to_go_tests:#?}");
+
+ for file in ready_to_go_tests {
+ let path_relative_to_webgpu_dir = file.strip_prefix(&webgpu_dir).unwrap();
+ let dst_path = cts_tests_dir.child(path_relative_to_webgpu_dir);
+ log::trace!("…moving {file} to {dst_path}…");
+ ensure!(
+ !fs::try_exists(&dst_path)?,
+ "internal error: duplicate path found while moving ready-to-go test {} to {}",
+ file,
+ dst_path,
+ );
+ fs::create_dir_all(dst_path.parent().unwrap()).wrap_err_with(|| {
+ format!(
+ concat!(
+ "failed to create destination parent dirs. ",
+ "while recursively moving from {} to {}",
+ ),
+ file, dst_path,
+ )
+ })?;
+ fs::rename(&file, &dst_path)
+ .wrap_err_with(|| format!("failed to move {file} to {dst_path}"))?;
+ }
+ log::debug!(" …finished moving ready-to-go WPT test files");
+
Ok(())
})?;